From 175f3a4c7fdcd9b6342b8248903a21047e4ac9aa Mon Sep 17 00:00:00 2001 From: veudayab Date: Mon, 10 Feb 2014 17:06:22 -0800 Subject: [PATCH] Azure Storage Client Library - 3.0.3 --- .../Core/Executor/Executor.cs | 207 ++++++++++-------- .../Core/Util/AsyncStreamCopier.cs | 66 ++++-- .../Core/Util/CancellableOperationBase.cs | 10 +- .../Table/DataServices/TableServiceContext.cs | 2 +- .../TableOperationHttpRequestFactory.cs | 17 +- .../TableOperationHttpResponseParsers.cs | 14 +- .../Table/TableBatchOperation.cs | 4 +- .../Table/TableOperation.cs | 24 +- Lib/ClassLibraryCommon/Table/TableQuery.cs | 2 +- .../Table/TableQueryNonGeneric.cs | 4 +- Lib/Common/Blob/BlobReadStreamBase.cs | 16 +- Lib/Common/Blob/CloudBlobContainer.Common.cs | 9 +- Lib/Common/Blob/CloudBlobDirectory.Common.cs | 67 ++---- Lib/Common/Blob/CloudBlockBlob.Common.cs | 12 +- Lib/Common/Blob/CloudPageBlob.Common.cs | 12 +- Lib/Common/Core/Executor/ExecutorBase.cs | 11 +- Lib/Common/Core/Executor/RESTCommand.cs | 3 + Lib/Common/Core/MultiBufferMemoryStream.cs | 1 + Lib/Common/Core/SR.cs | 2 + Lib/Common/Core/Util/NavigationHelper.cs | 93 ++++---- Lib/Common/Shared/Protocol/Constants.cs | 2 +- Lib/Common/StorageException.cs | 150 +++++++++---- Lib/Common/Table/CloudTableClient.Common.cs | 26 ++- Lib/WindowsAzure.Storage-Preview.nuspec | 2 +- Lib/WindowsDesktop/GlobalSuppressions.cs | 21 +- Lib/WindowsDesktop/Properties/AssemblyInfo.cs | 4 +- .../WindowsAzure.Storage.nuspec | 2 +- Lib/WindowsPhone/Properties/AssemblyInfo.cs | 4 +- Lib/WindowsPhone/Settings.StyleCop | 3 +- Lib/WindowsRuntime/Core/Executor/Executor.cs | 71 ++++-- Lib/WindowsRuntime/Properties/AssemblyInfo.cs | 4 +- Lib/WindowsRuntime/Settings.StyleCop | 1 + .../TableOperationHttpResponseParsers.cs | 6 +- .../Properties/AssemblyInfo.cs | 4 +- .../WindowsAzure.Storage.Table-Preview.nuspec | 4 +- .../Blob/CloudBlobDirectoryTest.cs | 63 ++++-- Test/ClassLibraryCommon/Core/LoggingTests.cs | 12 +- Test/Common/Core/StorageUriTests.cs | 2 +- .../WindowsDesktop/Properties/AssemblyInfo.cs | 4 +- .../Blob/BlobCancellationUnitTests.cs | 22 +- .../Blob/CloudBlobDirectoryTest.cs | 5 +- Test/WindowsPhone/Properties/AssemblyInfo.cs | 4 +- .../Blob/CloudBlobDirectoryTest.cs | 44 +++- .../WindowsRuntime/Properties/AssemblyInfo.cs | 4 +- changelog.txt | 10 + 45 files changed, 645 insertions(+), 405 deletions(-) diff --git a/Lib/ClassLibraryCommon/Core/Executor/Executor.cs b/Lib/ClassLibraryCommon/Core/Executor/Executor.cs index c42af843d..9d230113a 100644 --- a/Lib/ClassLibraryCommon/Core/Executor/Executor.cs +++ b/Lib/ClassLibraryCommon/Core/Executor/Executor.cs @@ -17,6 +17,7 @@ namespace Microsoft.WindowsAzure.Storage.Core.Executor { + using Microsoft.WindowsAzure.Storage.Auth; using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.Core.Util; using Microsoft.WindowsAzure.Storage.RetryPolicies; @@ -81,23 +82,20 @@ public static void InitRequest(ExecutionState executionState) return; } - lock (executionState.CancellationLockerObject) + if (Executor.CheckCancellation(executionState)) { - if (Executor.CheckCancellation(executionState)) - { - Executor.EndOperation(executionState); - return; - } + Executor.EndOperation(executionState); + return; + } - // 5. potentially upload data - if (executionState.RestCMD.SendStream != null) - { - Executor.BeginGetRequestStream(executionState); - } - else - { - Executor.BeginGetResponse(executionState); - } + // 5. potentially upload data + if (executionState.RestCMD.SendStream != null) + { + Executor.BeginGetRequestStream(executionState); + } + else + { + Executor.BeginGetResponse(executionState); } } catch (Exception ex) @@ -198,39 +196,36 @@ private static void EndSendStreamCopy(ExecutionState executionState) { executionState.CurrentOperation = ExecutorOperation.EndUploadRequest; - lock (executionState.CancellationLockerObject) - { - Executor.CheckCancellation(executionState); + Executor.CheckCancellation(executionState); - if (executionState.ExceptionRef != null) + if (executionState.ExceptionRef != null) + { + try { - try - { - executionState.Req.Abort(); - } - catch (Exception) - { - // No op - } - - Executor.EndOperation(executionState); + executionState.Req.Abort(); } - else + catch (Exception) { - try - { - executionState.ReqStream.Flush(); - executionState.ReqStream.Dispose(); - executionState.ReqStream = null; - } - catch (Exception) - { - // If we could not flush/dispose the request stream properly, - // BeginGetResponse will fail with a more meaningful error anyway. - } + // No op + } - Executor.BeginGetResponse(executionState); + Executor.EndOperation(executionState); + } + else + { + try + { + executionState.ReqStream.Flush(); + executionState.ReqStream.Dispose(); + executionState.ReqStream = null; + } + catch (Exception) + { + // If we could not flush/dispose the request stream properly, + // BeginGetResponse will fail with a more meaningful error anyway. } + + Executor.BeginGetResponse(executionState); } } #endregion @@ -289,7 +284,8 @@ private static void EndGetResponse(IAsyncResult getResponseResult) } else { - executionState.ExceptionRef = ExecutorBase.TranslateExceptionBasedOnParseError(ex, executionState.Cmd.CurrentResult, executionState.Resp, executionState.Cmd.ParseError); + // Store this exception for now. It will be parsed/thrown after the stream is read in step 8 + executionState.ExceptionRef = ex; } } @@ -300,21 +296,37 @@ private static void EndGetResponse(IAsyncResult getResponseResult) if (executionState.RestCMD.PreProcessResponse != null) { executionState.CurrentOperation = ExecutorOperation.PreProcess; - executionState.Result = executionState.RestCMD.PreProcessResponse(executionState.RestCMD, executionState.Resp, executionState.ExceptionRef, executionState.OperationContext); - // clear exception - executionState.ExceptionRef = null; - Logger.LogInformational(executionState.OperationContext, SR.TracePreProcessDone); + try + { + executionState.Result = executionState.RestCMD.PreProcessResponse(executionState.RestCMD, executionState.Resp, executionState.ExceptionRef, executionState.OperationContext); + + // clear exception + executionState.ExceptionRef = null; + Logger.LogInformational(executionState.OperationContext, SR.TracePreProcessDone); + } + catch (Exception ex) + { + executionState.ExceptionRef = ex; + } } Executor.CheckCancellation(executionState); + executionState.CurrentOperation = ExecutorOperation.GetResponseStream; + executionState.RestCMD.ResponseStream = executionState.Resp.GetResponseStream(); + // 8. (Potentially reads stream from server) - if (executionState.ExceptionRef == null) + if (executionState.ExceptionRef != null) { - executionState.CurrentOperation = ExecutorOperation.GetResponseStream; - executionState.RestCMD.ResponseStream = executionState.Resp.GetResponseStream(); + executionState.CurrentOperation = ExecutorOperation.BeginDownloadResponse; + Logger.LogInformational(executionState.OperationContext, SR.TraceDownloadError); + executionState.RestCMD.ErrorStream = new MemoryStream(); + executionState.RestCMD.ResponseStream.WriteToAsync(executionState.RestCMD.ErrorStream, null /* copyLength */, null /* maxLength */, false /* calculateMd5 */, executionState, new StreamDescriptor(), EndResponseStreamCopy); + } + else + { if (!executionState.RestCMD.RetrieveResponseStream) { executionState.RestCMD.DestinationStream = Stream.Null; @@ -337,11 +349,6 @@ private static void EndGetResponse(IAsyncResult getResponseResult) Executor.EndOperation(executionState); } } - else - { - // End - Executor.EndOperation(executionState); - } } catch (Exception ex) { @@ -353,6 +360,32 @@ private static void EndGetResponse(IAsyncResult getResponseResult) private static void EndResponseStreamCopy(ExecutionState executionState) { + // At this time, the response/error stream has been read. So try to parse the error if there was en exception + if (executionState.RestCMD.ErrorStream != null) + { + executionState.RestCMD.ErrorStream.Seek(0, SeekOrigin.Begin); + if (executionState.Cmd.ParseError != null) + { + executionState.ExceptionRef = StorageException.TranslateExceptionWithPreBufferedStream(executionState.ExceptionRef, executionState.Cmd.CurrentResult, stream => executionState.Cmd.ParseError(stream, executionState.Resp, null), executionState.RestCMD.ErrorStream); + } + else + { + executionState.ExceptionRef = StorageException.TranslateExceptionWithPreBufferedStream(executionState.ExceptionRef, executionState.Cmd.CurrentResult, null, executionState.RestCMD.ErrorStream); + } + + // Dispose the stream and end the operation + try + { + executionState.RestCMD.ErrorStream.Dispose(); + executionState.RestCMD.ErrorStream = null; + } + catch (Exception) + { + // no-op + } + } + + // Dispose the stream and end the operation try { if (executionState.RestCMD.ResponseStream != null) @@ -367,6 +400,7 @@ private static void EndResponseStreamCopy(ExecutionState executionState) } executionState.CurrentOperation = ExecutorOperation.EndDownloadResponse; + Executor.EndOperation(executionState); } #endregion @@ -377,49 +411,46 @@ private static void EndOperation(ExecutionState executionState) { Executor.FinishRequestAttempt(executionState); - lock (executionState.CancellationLockerObject) + try { - try - { - // If an operation has been canceled of timed out this should overwrite any exception - Executor.CheckCancellation(executionState); - Executor.CheckTimeout(executionState, true); + // If an operation has been canceled of timed out this should overwrite any exception + Executor.CheckCancellation(executionState); + Executor.CheckTimeout(executionState, true); - // Success - if (executionState.ExceptionRef == null) - { - // Step 9 - This will not be called if an exception is raised during stream copying - Executor.ProcessEndOfRequest(executionState); - executionState.OnComplete(); - return; - } - } - catch (Exception ex) + // Success + if (executionState.ExceptionRef == null) { - Logger.LogWarning(executionState.OperationContext, SR.TracePostProcessError, ex.Message); - executionState.ExceptionRef = ExecutorBase.TranslateExceptionBasedOnParseError(ex, executionState.Cmd.CurrentResult, executionState.Resp, executionState.Cmd.ParseError); + // Step 9 - This will not be called if an exception is raised during stream copying + Executor.ProcessEndOfRequest(executionState); + executionState.OnComplete(); + return; } - finally + } + catch (Exception ex) + { + Logger.LogWarning(executionState.OperationContext, SR.TracePostProcessError, ex.Message); + executionState.ExceptionRef = ExecutorBase.TranslateExceptionBasedOnParseError(ex, executionState.Cmd.CurrentResult, executionState.Resp, executionState.Cmd.ParseError); + } + finally + { + try { - try + if (executionState.ReqStream != null) { - if (executionState.ReqStream != null) - { - executionState.ReqStream.Dispose(); - executionState.ReqStream = null; - } - - if (executionState.Resp != null) - { - executionState.Resp.Close(); - executionState.Resp = null; - } + executionState.ReqStream.Dispose(); + executionState.ReqStream = null; } - catch (Exception) + + if (executionState.Resp != null) { - // no op + executionState.Resp.Close(); + executionState.Resp = null; } } + catch (Exception) + { + // no op + } } // Handle Retry diff --git a/Lib/ClassLibraryCommon/Core/Util/AsyncStreamCopier.cs b/Lib/ClassLibraryCommon/Core/Util/AsyncStreamCopier.cs index 9365e5391..8028e8078 100644 --- a/Lib/ClassLibraryCommon/Core/Util/AsyncStreamCopier.cs +++ b/Lib/ClassLibraryCommon/Core/Util/AsyncStreamCopier.cs @@ -34,6 +34,9 @@ internal class AsyncStreamCopier : IDisposable private volatile int currentWriteCount = -1; private StreamDescriptor streamCopyState = null; + // This keeps track of any exceptions that happens during the copy itself and is set on the executionState at the end. + private Exception exceptionRef = null; + // This variable keeps track of bytes that have already been read from the source stream. // It should only be modified using Interlocked.Add and read with Interlocked.Read. private long currentBytesReadFromSource = 0; @@ -121,7 +124,7 @@ public void StartCopyStream(Action> completedDelegate, long? c this.waitHandle = ThreadPool.RegisterWaitForSingleObject( this.completedEvent, AsyncStreamCopier.MaximumCopyTimeCallback, - this.state, + this, this.state.RemainingTimeout, true); } @@ -142,7 +145,7 @@ public void StartCopyStream(Action> completedDelegate, long? c public void Abort() { this.cancelRequested = true; - AsyncStreamCopier.ForceAbort(this.state, false); + AsyncStreamCopier.ForceAbort(this, false); } /// @@ -191,15 +194,15 @@ private void EndOpWithCatch(IAsyncResult res) { if (this.state.ReqTimedOut) { - this.state.ExceptionRef = Exceptions.GenerateTimeoutException(this.state.Cmd != null ? this.state.Cmd.CurrentResult : null, ex); + this.exceptionRef = Exceptions.GenerateTimeoutException(this.state.Cmd != null ? this.state.Cmd.CurrentResult : null, ex); } else if (this.cancelRequested) { - this.state.ExceptionRef = Exceptions.GenerateCancellationException(this.state.Cmd != null ? this.state.Cmd.CurrentResult : null, ex); + this.exceptionRef = Exceptions.GenerateCancellationException(this.state.Cmd != null ? this.state.Cmd.CurrentResult : null, ex); } else { - this.state.ExceptionRef = ex; + this.exceptionRef = ex; } // if there is an outstanding read/write let it signal completion since we populated the exception. @@ -280,9 +283,9 @@ private void EndOperation(IAsyncResult res) // If nothing more needs to be read and no write operation is scheduled, we are finished. if (this.ReachedEndOfSrc() && this.writeRes == null) { - if (this.state.ExceptionRef == null && this.copyLen.HasValue && this.NextReadLength() != 0) + if (this.exceptionRef == null && this.copyLen.HasValue && this.NextReadLength() != 0) { - this.state.ExceptionRef = new ArgumentOutOfRangeException("copyLength", SR.StreamLengthShortError); + this.exceptionRef = new ArgumentOutOfRangeException("copyLength", SR.StreamLengthShortError); } this.SignalCompletion(); @@ -292,31 +295,33 @@ private void EndOperation(IAsyncResult res) /// /// Callback for timeout timer. Aborts the AsyncStreamCopier operation if a timeout occurs. /// - /// Callback state. + /// AsyncStreamCopier operation. /// True if the timer has timed out, false otherwise. - private static void MaximumCopyTimeCallback(object state, bool timedOut) + private static void MaximumCopyTimeCallback(object copier, bool timedOut) { if (timedOut) { - ExecutionState executionState = (ExecutionState)state; - AsyncStreamCopier.ForceAbort(executionState, true); + AsyncStreamCopier asyncCopier = (AsyncStreamCopier)copier; + AsyncStreamCopier.ForceAbort(asyncCopier, true); } } /// /// Aborts the AsyncStreamCopier operation. /// - /// An object that stores state of the operation. + /// AsyncStreamCopier operation. /// True if aborted due to a time out, or false for a general cancellation. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Reviewed.")] - private static void ForceAbort(ExecutionState executionState, bool timedOut) + private static void ForceAbort(AsyncStreamCopier copier, bool timedOut) { - if (executionState.Req != null) + if (copier.state.Req != null) { try { - executionState.ReqTimedOut = timedOut; - executionState.Req.Abort(); + copier.state.ReqTimedOut = timedOut; +#if !WINDOWS_PHONE + copier.state.Req.Abort(); +#endif } catch (Exception) { @@ -324,9 +329,9 @@ private static void ForceAbort(ExecutionState executionState, bool timedOut) } } - executionState.ExceptionRef = timedOut ? - Exceptions.GenerateTimeoutException(executionState.Cmd != null ? executionState.Cmd.CurrentResult : null, null) : - Exceptions.GenerateCancellationException(executionState.Cmd != null ? executionState.Cmd.CurrentResult : null, null); + copier.exceptionRef = timedOut ? + Exceptions.GenerateTimeoutException(copier.state.Cmd != null ? copier.state.Cmd.CurrentResult : null, null) : + Exceptions.GenerateCancellationException(copier.state.Cmd != null ? copier.state.Cmd.CurrentResult : null, null); } /// @@ -352,13 +357,20 @@ private void ProcessCompletion() // Re hookup cancellation delegate this.state.CancelDelegate = this.previousCancellationDelegate; +#if WINDOWS_PHONE + if ((this.cancelRequested || this.state.ReqTimedOut) && this.state.Req != null) + { + this.state.Req.Abort(); + } +#endif + // clear references this.src = null; this.dest = null; this.currentReadBuff = null; this.currentWriteBuff = null; - if (this.state.ExceptionRef == null && + if (this.exceptionRef == null && this.streamCopyState != null && this.streamCopyState.Md5HashRef != null) { @@ -376,6 +388,12 @@ private void ProcessCompletion() } } + // set the exceptionRef on the execution state + if (this.exceptionRef != null) + { + this.state.ExceptionRef = this.exceptionRef; + } + // invoke the caller's callback Action> callback = this.completedDel; this.completedDel = null; @@ -403,20 +421,20 @@ private bool ShouldDispatchNextOperation() { if (this.maximumLen.HasValue && Interlocked.Read(ref this.currentBytesReadFromSource) > this.maximumLen) { - this.state.ExceptionRef = new InvalidOperationException(SR.StreamLengthError); + this.exceptionRef = new InvalidOperationException(SR.StreamLengthError); } else if (this.state.OperationExpiryTime.HasValue && DateTime.Now >= this.state.OperationExpiryTime.Value) { - this.state.ExceptionRef = Exceptions.GenerateTimeoutException(this.state.Cmd != null ? this.state.Cmd.CurrentResult : null, null); + this.exceptionRef = Exceptions.GenerateTimeoutException(this.state.Cmd != null ? this.state.Cmd.CurrentResult : null, null); } else if (this.state.CancelRequested) { - this.state.ExceptionRef = Exceptions.GenerateCancellationException(this.state.Cmd != null ? this.state.Cmd.CurrentResult : null, null); + this.exceptionRef = Exceptions.GenerateCancellationException(this.state.Cmd != null ? this.state.Cmd.CurrentResult : null, null); } // note cancellation will new up a exception and store it, so this will be not null; // continue if no exceptions so far - return !this.cancelRequested && this.state.ExceptionRef == null; + return !this.cancelRequested && this.exceptionRef == null; } /// diff --git a/Lib/ClassLibraryCommon/Core/Util/CancellableOperationBase.cs b/Lib/ClassLibraryCommon/Core/Util/CancellableOperationBase.cs index 8138c24ea..6e9598218 100644 --- a/Lib/ClassLibraryCommon/Core/Util/CancellableOperationBase.cs +++ b/Lib/ClassLibraryCommon/Core/Util/CancellableOperationBase.cs @@ -49,18 +49,12 @@ internal Action CancelDelegate { get { - lock (this.cancellationLockerObject) - { - return this.cancelDelegate; - } + return this.cancelDelegate; } set { - lock (this.cancellationLockerObject) - { - this.cancelDelegate = value; - } + this.cancelDelegate = value; } } diff --git a/Lib/ClassLibraryCommon/Table/DataServices/TableServiceContext.cs b/Lib/ClassLibraryCommon/Table/DataServices/TableServiceContext.cs index 487513b35..d0af346c4 100644 --- a/Lib/ClassLibraryCommon/Table/DataServices/TableServiceContext.cs +++ b/Lib/ClassLibraryCommon/Table/DataServices/TableServiceContext.cs @@ -75,7 +75,7 @@ public TableServiceContext(CloudTableClient client) // Since the default is JSON light, this is valid. If users change it to Atom or NoMetadata, this gets updated. if (this.payloadFormat == TablePayloadFormat.Json) { - this.Format.UseJson(new TableStorageModel(NavigationHelper.GetAccountNameFromUri(client.BaseUri, client.UsePathStyleUris))); + this.Format.UseJson(new TableStorageModel(client.AccountName)); } } diff --git a/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpRequestFactory.cs b/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpRequestFactory.cs index 1ae5e41b1..59f1abe51 100644 --- a/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpRequestFactory.cs +++ b/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpRequestFactory.cs @@ -204,29 +204,30 @@ private static void WriteOdataEntity(ITableEntity entity, TableOperationType ope #region TableEntity Serialization Helpers - internal static List GetPropertiesFromDictionary(IDictionary properties) + internal static IEnumerable GetPropertiesFromDictionary(IDictionary properties) { - return properties.Select(kvp => new ODataProperty() { Name = kvp.Key, Value = kvp.Value.PropertyAsObject }).ToList(); + return properties.Select(kvp => new ODataProperty() { Name = kvp.Key, Value = kvp.Value.PropertyAsObject }); } - internal static List GetPropertiesWithKeys(ITableEntity entity, OperationContext operationContext, TableOperationType operationType) + internal static IEnumerable GetPropertiesWithKeys(ITableEntity entity, OperationContext operationContext, TableOperationType operationType) { - List retProps = GetPropertiesFromDictionary(entity.WriteEntity(operationContext)); - if (operationType == TableOperationType.Insert) { if (entity.PartitionKey != null) { - retProps.Add(new ODataProperty() { Name = TableConstants.PartitionKey, Value = entity.PartitionKey }); + yield return new ODataProperty() { Name = TableConstants.PartitionKey, Value = entity.PartitionKey }; } if (entity.RowKey != null) { - retProps.Add(new ODataProperty() { Name = TableConstants.RowKey, Value = entity.RowKey }); + yield return new ODataProperty() { Name = TableConstants.RowKey, Value = entity.RowKey }; } } - return retProps; + foreach (ODataProperty property in GetPropertiesFromDictionary(entity.WriteEntity(operationContext))) + { + yield return property; + } } #endregion diff --git a/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpResponseParsers.cs b/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpResponseParsers.cs index 3cf118ba3..7776c1c64 100644 --- a/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpResponseParsers.cs +++ b/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpResponseParsers.cs @@ -20,6 +20,7 @@ namespace Microsoft.WindowsAzure.Storage.Table.Protocol using Microsoft.Data.OData; using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.Core.Executor; + using Microsoft.WindowsAzure.Storage.Core.Util; using Microsoft.WindowsAzure.Storage.Shared.Protocol; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -37,7 +38,7 @@ namespace Microsoft.WindowsAzure.Storage.Table.Protocol internal class TableOperationHttpResponseParsers { - internal static TableResult TableOperationPreProcess(TableResult result, TableOperation operation, HttpWebResponse resp, Exception ex, StorageCommandBase cmd) + internal static TableResult TableOperationPreProcess(TableResult result, TableOperation operation, HttpWebResponse resp, Exception ex) { result.HttpStatusCode = (int)resp.StatusCode; @@ -45,14 +46,15 @@ internal static TableResult TableOperationPreProcess(TableResult result, Tabl { if (resp.StatusCode != HttpStatusCode.OK && resp.StatusCode != HttpStatusCode.NotFound) { - throw ExecutorBase.TranslateExceptionBasedOnParseError(ex, cmd.CurrentResult, resp, cmd.ParseError); + CommonUtility.AssertNotNull("ex", ex); + throw ex; } } else { if (ex != null) { - throw ExecutorBase.TranslateExceptionBasedOnParseError(ex, cmd.CurrentResult, resp, cmd.ParseError); + throw ex; } else if (operation.OperationType == TableOperationType.Insert) { @@ -60,14 +62,14 @@ internal static TableResult TableOperationPreProcess(TableResult result, Tabl { if (resp.StatusCode != HttpStatusCode.Created) { - throw ExecutorBase.TranslateExceptionBasedOnParseError(ex, cmd.CurrentResult, resp, cmd.ParseError); + throw ex; } } else { if (resp.StatusCode != HttpStatusCode.NoContent) { - throw ExecutorBase.TranslateExceptionBasedOnParseError(ex, cmd.CurrentResult, resp, cmd.ParseError); + throw ex; } } } @@ -75,7 +77,7 @@ internal static TableResult TableOperationPreProcess(TableResult result, Tabl { if (resp.StatusCode != HttpStatusCode.NoContent) { - throw ExecutorBase.TranslateExceptionBasedOnParseError(ex, cmd.CurrentResult, resp, cmd.ParseError); + throw ex; } } } diff --git a/Lib/ClassLibraryCommon/Table/TableBatchOperation.cs b/Lib/ClassLibraryCommon/Table/TableBatchOperation.cs index edfeee723..f35817190 100644 --- a/Lib/ClassLibraryCommon/Table/TableBatchOperation.cs +++ b/Lib/ClassLibraryCommon/Table/TableBatchOperation.cs @@ -126,13 +126,13 @@ private static RESTCommand> BatchImpl(TableBatchOperation bat batchCmd.ParseError = StorageExtendedErrorInformation.ReadFromStreamUsingODataLib; batchCmd.BuildRequestDelegate = (uri, builder, timeout, ctx) => { - Tuple res = TableOperationHttpWebRequestFactory.BuildRequestForTableBatchOperation(uri, builder, client.BufferManager, timeout, table.Name, batch, ctx, requestOptions.PayloadFormat.Value, NavigationHelper.GetAccountNameFromUri(client.BaseUri, client.UsePathStyleUris)); + Tuple res = TableOperationHttpWebRequestFactory.BuildRequestForTableBatchOperation(uri, builder, client.BufferManager, timeout, table.Name, batch, ctx, requestOptions.PayloadFormat.Value, client.AccountName); batchCmd.SendStream = res.Item2; return res.Item1; }; batchCmd.PreProcessResponse = (cmd, resp, ex, ctx) => HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Accepted, resp != null ? resp.StatusCode : HttpStatusCode.Unused, results, cmd, ex); - batchCmd.PostProcessResponse = (cmd, resp, ctx) => TableOperationHttpResponseParsers.TableBatchOperationPostProcess(results, batch, cmd, resp, ctx, requestOptions, NavigationHelper.GetAccountNameFromUri(client.BaseUri, client.UsePathStyleUris)); + batchCmd.PostProcessResponse = (cmd, resp, ctx) => TableOperationHttpResponseParsers.TableBatchOperationPostProcess(results, batch, cmd, resp, ctx, requestOptions, client.AccountName); batchCmd.RecoveryAction = (cmd, ex, ctx) => results.Clear(); return batchCmd; diff --git a/Lib/ClassLibraryCommon/Table/TableOperation.cs b/Lib/ClassLibraryCommon/Table/TableOperation.cs index 1a2faad9b..3fbb07a9b 100644 --- a/Lib/ClassLibraryCommon/Table/TableOperation.cs +++ b/Lib/ClassLibraryCommon/Table/TableOperation.cs @@ -178,14 +178,14 @@ private static RESTCommand InsertImpl(TableOperation operation, Clo insertCmd.ParseError = StorageExtendedErrorInformation.ReadFromStreamUsingODataLib; insertCmd.BuildRequestDelegate = (uri, builder, timeout, ctx) => { - Tuple res = TableOperationHttpWebRequestFactory.BuildRequestForTableOperation(uri, builder, client.BufferManager, timeout, operation, ctx, requestOptions.PayloadFormat.Value, NavigationHelper.GetAccountNameFromUri(client.BaseUri, client.UsePathStyleUris)); + Tuple res = TableOperationHttpWebRequestFactory.BuildRequestForTableOperation(uri, builder, client.BufferManager, timeout, operation, ctx, requestOptions.PayloadFormat.Value, client.AccountName); insertCmd.SendStream = res.Item2; return res.Item1; }; - insertCmd.PreProcessResponse = (cmd, resp, ex, ctx) => TableOperationHttpResponseParsers.TableOperationPreProcess(result, operation, resp, ex, cmd); + insertCmd.PreProcessResponse = (cmd, resp, ex, ctx) => TableOperationHttpResponseParsers.TableOperationPreProcess(result, operation, resp, ex); - insertCmd.PostProcessResponse = (cmd, resp, ctx) => TableOperationHttpResponseParsers.TableOperationPostProcess(result, operation, cmd, resp, ctx, requestOptions, NavigationHelper.GetAccountNameFromUri(client.BaseUri, client.UsePathStyleUris)); + insertCmd.PostProcessResponse = (cmd, resp, ctx) => TableOperationHttpResponseParsers.TableOperationPostProcess(result, operation, cmd, resp, ctx, requestOptions, client.AccountName); return insertCmd; } @@ -199,8 +199,8 @@ private static RESTCommand DeleteImpl(TableOperation operation, Clo deleteCmd.RetrieveResponseStream = false; deleteCmd.SignRequest = client.AuthenticationHandler.SignRequest; deleteCmd.ParseError = StorageExtendedErrorInformation.ReadFromStreamUsingODataLib; - deleteCmd.BuildRequestDelegate = (uri, builder, timeout, ctx) => TableOperationHttpWebRequestFactory.BuildRequestForTableOperation(uri, builder, client.BufferManager, timeout, operation, ctx, requestOptions.PayloadFormat.Value, NavigationHelper.GetAccountNameFromUri(client.BaseUri, client.UsePathStyleUris)).Item1; - deleteCmd.PreProcessResponse = (cmd, resp, ex, ctx) => TableOperationHttpResponseParsers.TableOperationPreProcess(result, operation, resp, ex, cmd); + deleteCmd.BuildRequestDelegate = (uri, builder, timeout, ctx) => TableOperationHttpWebRequestFactory.BuildRequestForTableOperation(uri, builder, client.BufferManager, timeout, operation, ctx, requestOptions.PayloadFormat.Value, client.AccountName).Item1; + deleteCmd.PreProcessResponse = (cmd, resp, ex, ctx) => TableOperationHttpResponseParsers.TableOperationPreProcess(result, operation, resp, ex); return deleteCmd; } @@ -216,12 +216,12 @@ private static RESTCommand MergeImpl(TableOperation operation, Clou mergeCmd.ParseError = StorageExtendedErrorInformation.ReadFromStreamUsingODataLib; mergeCmd.BuildRequestDelegate = (uri, builder, timeout, ctx) => { - Tuple res = TableOperationHttpWebRequestFactory.BuildRequestForTableOperation(uri, builder, client.BufferManager, timeout, operation, ctx, requestOptions.PayloadFormat.Value, NavigationHelper.GetAccountNameFromUri(client.BaseUri, client.UsePathStyleUris)); + Tuple res = TableOperationHttpWebRequestFactory.BuildRequestForTableOperation(uri, builder, client.BufferManager, timeout, operation, ctx, requestOptions.PayloadFormat.Value, client.AccountName); mergeCmd.SendStream = res.Item2; return res.Item1; }; - mergeCmd.PreProcessResponse = (cmd, resp, ex, ctx) => TableOperationHttpResponseParsers.TableOperationPreProcess(result, operation, resp, ex, cmd); + mergeCmd.PreProcessResponse = (cmd, resp, ex, ctx) => TableOperationHttpResponseParsers.TableOperationPreProcess(result, operation, resp, ex); return mergeCmd; } @@ -237,12 +237,12 @@ private static RESTCommand ReplaceImpl(TableOperation operation, Cl replaceCmd.ParseError = StorageExtendedErrorInformation.ReadFromStreamUsingODataLib; replaceCmd.BuildRequestDelegate = (uri, builder, timeout, ctx) => { - Tuple res = TableOperationHttpWebRequestFactory.BuildRequestForTableOperation(uri, builder, client.BufferManager, timeout, operation, ctx, requestOptions.PayloadFormat.Value, NavigationHelper.GetAccountNameFromUri(client.BaseUri, client.UsePathStyleUris)); + Tuple res = TableOperationHttpWebRequestFactory.BuildRequestForTableOperation(uri, builder, client.BufferManager, timeout, operation, ctx, requestOptions.PayloadFormat.Value, client.AccountName); replaceCmd.SendStream = res.Item2; return res.Item1; }; - replaceCmd.PreProcessResponse = (cmd, resp, ex, ctx) => TableOperationHttpResponseParsers.TableOperationPreProcess(result, operation, resp, ex, cmd); + replaceCmd.PreProcessResponse = (cmd, resp, ex, ctx) => TableOperationHttpResponseParsers.TableOperationPreProcess(result, operation, resp, ex); return replaceCmd; } @@ -257,8 +257,8 @@ private static RESTCommand RetrieveImpl(TableOperation operation, C retrieveCmd.RetrieveResponseStream = true; retrieveCmd.SignRequest = client.AuthenticationHandler.SignRequest; retrieveCmd.ParseError = StorageExtendedErrorInformation.ReadFromStreamUsingODataLib; - retrieveCmd.BuildRequestDelegate = (uri, builder, timeout, ctx) => TableOperationHttpWebRequestFactory.BuildRequestForTableOperation(uri, builder, client.BufferManager, timeout, operation, ctx, requestOptions.PayloadFormat.Value, NavigationHelper.GetAccountNameFromUri(client.BaseUri, client.UsePathStyleUris)).Item1; - retrieveCmd.PreProcessResponse = (cmd, resp, ex, ctx) => TableOperationHttpResponseParsers.TableOperationPreProcess(result, operation, resp, ex, cmd); + retrieveCmd.BuildRequestDelegate = (uri, builder, timeout, ctx) => TableOperationHttpWebRequestFactory.BuildRequestForTableOperation(uri, builder, client.BufferManager, timeout, operation, ctx, requestOptions.PayloadFormat.Value, client.AccountName).Item1; + retrieveCmd.PreProcessResponse = (cmd, resp, ex, ctx) => TableOperationHttpResponseParsers.TableOperationPreProcess(result, operation, resp, ex); retrieveCmd.PostProcessResponse = (cmd, resp, ctx) => { if (resp.StatusCode == HttpStatusCode.NotFound) @@ -266,7 +266,7 @@ private static RESTCommand RetrieveImpl(TableOperation operation, C return result; } - result = TableOperationHttpResponseParsers.TableOperationPostProcess(result, operation, cmd, resp, ctx, requestOptions, NavigationHelper.GetAccountNameFromUri(client.BaseUri, client.UsePathStyleUris)); + result = TableOperationHttpResponseParsers.TableOperationPostProcess(result, operation, cmd, resp, ctx, requestOptions, client.AccountName); return result; }; diff --git a/Lib/ClassLibraryCommon/Table/TableQuery.cs b/Lib/ClassLibraryCommon/Table/TableQuery.cs index f0f89a7bc..ff89d8b59 100644 --- a/Lib/ClassLibraryCommon/Table/TableQuery.cs +++ b/Lib/ClassLibraryCommon/Table/TableQuery.cs @@ -553,7 +553,7 @@ private static RESTCommand> QueryImpl HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp != null ? resp.StatusCode : HttpStatusCode.Unused, null /* retVal */, cmd, ex); queryCmd.PostProcessResponse = (cmd, resp, ctx) => { - ResultSegment resSeg = TableOperationHttpResponseParsers.TableQueryPostProcessGeneric(cmd.ResponseStream, resolver.Invoke, resp, requestOptions, ctx, NavigationHelper.GetAccountNameFromUri(client.BaseUri, client.UsePathStyleUris)); + ResultSegment resSeg = TableOperationHttpResponseParsers.TableQueryPostProcessGeneric(cmd.ResponseStream, resolver.Invoke, resp, requestOptions, ctx, client.AccountName); if (resSeg.ContinuationToken != null) { resSeg.ContinuationToken.TargetLocation = cmd.CurrentResult.TargetLocation; diff --git a/Lib/ClassLibraryCommon/Table/TableQueryNonGeneric.cs b/Lib/ClassLibraryCommon/Table/TableQueryNonGeneric.cs index 21f316cfd..8c89ad85b 100644 --- a/Lib/ClassLibraryCommon/Table/TableQueryNonGeneric.cs +++ b/Lib/ClassLibraryCommon/Table/TableQueryNonGeneric.cs @@ -175,7 +175,7 @@ private static RESTCommand> QueryImpl(Tabl queryCmd.PreProcessResponse = (cmd, resp, ex, ctx) => HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp != null ? resp.StatusCode : HttpStatusCode.Unused, null /* retVal */, cmd, ex); queryCmd.PostProcessResponse = (cmd, resp, ctx) => { - ResultSegment resSeg = TableOperationHttpResponseParsers.TableQueryPostProcessGeneric(cmd.ResponseStream, EntityUtilities.ResolveDynamicEntity, resp, requestOptions, ctx, NavigationHelper.GetAccountNameFromUri(client.BaseUri, client.UsePathStyleUris)); + ResultSegment resSeg = TableOperationHttpResponseParsers.TableQueryPostProcessGeneric(cmd.ResponseStream, EntityUtilities.ResolveDynamicEntity, resp, requestOptions, ctx, client.AccountName); if (resSeg.ContinuationToken != null) { resSeg.ContinuationToken.TargetLocation = cmd.CurrentResult.TargetLocation; @@ -209,7 +209,7 @@ private static RESTCommand> QueryImpl HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp != null ? resp.StatusCode : HttpStatusCode.Unused, null /* retVal */, cmd, ex); queryCmd.PostProcessResponse = (cmd, resp, ctx) => { - ResultSegment resSeg = TableOperationHttpResponseParsers.TableQueryPostProcessGeneric(cmd.ResponseStream, resolver.Invoke, resp, requestOptions, ctx, NavigationHelper.GetAccountNameFromUri(client.BaseUri, client.UsePathStyleUris)); + ResultSegment resSeg = TableOperationHttpResponseParsers.TableQueryPostProcessGeneric(cmd.ResponseStream, resolver.Invoke, resp, requestOptions, ctx, client.AccountName); if (resSeg.ContinuationToken != null) { resSeg.ContinuationToken.TargetLocation = cmd.CurrentResult.TargetLocation; diff --git a/Lib/Common/Blob/BlobReadStreamBase.cs b/Lib/Common/Blob/BlobReadStreamBase.cs index 1e2bc4ffc..df51aee33 100644 --- a/Lib/Common/Blob/BlobReadStreamBase.cs +++ b/Lib/Common/Blob/BlobReadStreamBase.cs @@ -31,7 +31,7 @@ internal abstract class BlobReadStreamBase : Stream protected ICloudBlob blob; protected BlobProperties blobProperties; protected long currentOffset; - protected MemoryStream internalBuffer; + protected MultiBufferMemoryStream internalBuffer; protected int streamMinimumReadSizeInBytes; protected AccessCondition accessCondition; protected BlobRequestOptions options; @@ -57,7 +57,7 @@ protected BlobReadStreamBase(ICloudBlob blob, AccessCondition accessCondition, B this.blobProperties = new BlobProperties(blob.Properties); this.currentOffset = 0; this.streamMinimumReadSizeInBytes = this.blob.StreamMinimumReadSizeInBytes; - this.internalBuffer = new MemoryStream(this.streamMinimumReadSizeInBytes); + this.internalBuffer = new MultiBufferMemoryStream(blob.ServiceClient.BufferManager); this.accessCondition = accessCondition; this.options = options; this.operationContext = operationContext; @@ -280,17 +280,17 @@ protected override void Dispose(bool disposing) { if (disposing) { - if (this.blobMD5 != null) - { - this.blobMD5.Dispose(); - this.blobMD5 = null; - } - if (this.internalBuffer != null) { this.internalBuffer.Dispose(); this.internalBuffer = null; } + + if (this.blobMD5 != null) + { + this.blobMD5.Dispose(); + this.blobMD5 = null; + } } base.Dispose(disposing); diff --git a/Lib/Common/Blob/CloudBlobContainer.Common.cs b/Lib/Common/Blob/CloudBlobContainer.Common.cs index f35d418d7..26cbe5631 100644 --- a/Lib/Common/Blob/CloudBlobContainer.Common.cs +++ b/Lib/Common/Blob/CloudBlobContainer.Common.cs @@ -263,9 +263,14 @@ public CloudBlockBlob GetBlockBlobReference(string blobName, DateTimeOffset? sna /// A reference to a virtual blob directory. public CloudBlobDirectory GetDirectoryReference(string relativeAddress) { - CommonUtility.AssertNotNullOrEmpty("relativeAddress", relativeAddress); + CommonUtility.AssertNotNull("relativeAddress", relativeAddress); + if (!string.IsNullOrEmpty(relativeAddress) && !relativeAddress.EndsWith(this.ServiceClient.DefaultDelimiter, StringComparison.Ordinal)) + { + relativeAddress = relativeAddress + this.ServiceClient.DefaultDelimiter; + } + StorageUri blobDirectoryUri = NavigationHelper.AppendPathToUri(this.StorageUri, relativeAddress); - return new CloudBlobDirectory(blobDirectoryUri.PrimaryUri.AbsoluteUri, this); + return new CloudBlobDirectory(blobDirectoryUri, relativeAddress, this); } } } diff --git a/Lib/Common/Blob/CloudBlobDirectory.Common.cs b/Lib/Common/Blob/CloudBlobDirectory.Common.cs index 676d6126c..dc341bc37 100644 --- a/Lib/Common/Blob/CloudBlobDirectory.Common.cs +++ b/Lib/Common/Blob/CloudBlobDirectory.Common.cs @@ -31,31 +31,22 @@ public sealed partial class CloudBlobDirectory : IListBlobItem /// private CloudBlobDirectory parent; - /// - /// Stores the prefix this directory represents. - /// - private string prefix; - /// /// Initializes a new instance of the class given an address and a client. /// - /// The blob directory's address. + /// The blob directory's Uri. + /// The blob directory's prefix. /// The container for the virtual directory. - internal CloudBlobDirectory(string absolutePath, CloudBlobContainer container) + internal CloudBlobDirectory(StorageUri uri, string prefix, CloudBlobContainer container) { - CommonUtility.AssertNotNullOrEmpty("absolutePath", absolutePath); + CommonUtility.AssertNotNull("uri", uri); + CommonUtility.AssertNotNull("prefix", prefix); CommonUtility.AssertNotNull("container", container); this.ServiceClient = container.ServiceClient; this.Container = container; - - string delimiter = Uri.EscapeUriString(this.ServiceClient.DefaultDelimiter); - if (!absolutePath.EndsWith(delimiter, StringComparison.Ordinal)) - { - absolutePath = absolutePath + delimiter; - } - - this.StorageUri = NavigationHelper.AppendPathToUri(this.ServiceClient.StorageUri, absolutePath); + this.Prefix = prefix; + this.StorageUri = uri; } /// @@ -76,7 +67,6 @@ public Uri Uri } } - /// /// Gets the blob directory's URIs for all locations. /// @@ -99,15 +89,15 @@ public CloudBlobDirectory Parent { if (this.parent == null) { - StorageUri parentUri = NavigationHelper.GetParentAddress( - this.StorageUri, - this.ServiceClient.DefaultDelimiter, - this.ServiceClient.UsePathStyleUris); + string parentName = NavigationHelper.GetParentName(this.StorageUri, this.ServiceClient.DefaultDelimiter, this.ServiceClient.UsePathStyleUris); - if (parentUri != null) + if (parentName != null) { + StorageUri parentUri = NavigationHelper.AppendPathToUri(this.Container.StorageUri, parentName); + this.parent = new CloudBlobDirectory( - parentUri.PrimaryUri.AbsoluteUri, + parentUri, + parentName, this.Container); } } @@ -120,18 +110,7 @@ public CloudBlobDirectory Parent /// Gets the prefix. /// /// The prefix. - public string Prefix - { - get - { - if (this.prefix == null) - { - this.InitializePrefix(); - } - - return this.prefix; - } - } + public string Prefix { get; private set; } /// /// Gets a reference to a page blob in this virtual directory. @@ -189,19 +168,13 @@ public CloudBlockBlob GetBlockBlobReference(string blobName, DateTimeOffset? sna public CloudBlobDirectory GetSubdirectoryReference(string itemName) { CommonUtility.AssertNotNull("itemName", itemName); - StorageUri subdirectoryUri = NavigationHelper.AppendPathToUri(this.StorageUri, itemName, this.ServiceClient.DefaultDelimiter); - return new CloudBlobDirectory(subdirectoryUri.PrimaryUri.AbsoluteUri, this.Container); - } - - /// - /// Initializes the prefix. - /// - private void InitializePrefix() - { - // Need to add the trailing slash or MakeRelativeUri will return the containerName again - Uri parentUri = new Uri(this.Container.Uri + NavigationHelper.Slash); + if (!string.IsNullOrEmpty(itemName) && !itemName.EndsWith(this.ServiceClient.DefaultDelimiter, StringComparison.Ordinal)) + { + itemName = itemName + this.ServiceClient.DefaultDelimiter; + } - this.prefix = Uri.UnescapeDataString(parentUri.MakeRelativeUri(this.Uri).OriginalString); + StorageUri subdirectoryUri = NavigationHelper.AppendPathToUri(this.StorageUri, itemName, this.ServiceClient.DefaultDelimiter); + return new CloudBlobDirectory(subdirectoryUri, this.Prefix + itemName, this.Container); } } } \ No newline at end of file diff --git a/Lib/Common/Blob/CloudBlockBlob.Common.cs b/Lib/Common/Blob/CloudBlockBlob.Common.cs index 8e9f43550..758ea9d3c 100644 --- a/Lib/Common/Blob/CloudBlockBlob.Common.cs +++ b/Lib/Common/Blob/CloudBlockBlob.Common.cs @@ -371,15 +371,15 @@ public CloudBlobDirectory Parent { if (this.parent == null) { - StorageUri parentUri = NavigationHelper.GetParentAddress( - this.StorageUri, - this.ServiceClient.DefaultDelimiter, - this.ServiceClient.UsePathStyleUris); + string parentName = NavigationHelper.GetParentName(this.StorageUri, this.ServiceClient.DefaultDelimiter, this.ServiceClient.UsePathStyleUris); - if (parentUri != null) + if (parentName != null) { + StorageUri parentUri = NavigationHelper.AppendPathToUri(this.Container.StorageUri, parentName); + this.parent = new CloudBlobDirectory( - parentUri.PrimaryUri.AbsoluteUri, + parentUri, + parentName, this.Container); } } diff --git a/Lib/Common/Blob/CloudPageBlob.Common.cs b/Lib/Common/Blob/CloudPageBlob.Common.cs index a33359cd5..e4b8d9678 100644 --- a/Lib/Common/Blob/CloudPageBlob.Common.cs +++ b/Lib/Common/Blob/CloudPageBlob.Common.cs @@ -368,15 +368,15 @@ public CloudBlobDirectory Parent { if (this.parent == null) { - StorageUri parentUri = NavigationHelper.GetParentAddress( - this.StorageUri, - this.ServiceClient.DefaultDelimiter, - this.ServiceClient.UsePathStyleUris); + string parentName = NavigationHelper.GetParentName(this.StorageUri, this.ServiceClient.DefaultDelimiter, this.ServiceClient.UsePathStyleUris); - if (parentUri != null) + if (parentName != null) { + StorageUri parentUri = NavigationHelper.AppendPathToUri(this.Container.StorageUri, parentName); + this.parent = new CloudBlobDirectory( - parentUri.PrimaryUri.AbsoluteUri, + parentUri, + parentName, this.Container); } } diff --git a/Lib/Common/Core/Executor/ExecutorBase.cs b/Lib/Common/Core/Executor/ExecutorBase.cs index b7dd50080..7ed12e94b 100644 --- a/Lib/Common/Core/Executor/ExecutorBase.cs +++ b/Lib/Common/Core/Executor/ExecutorBase.cs @@ -167,12 +167,15 @@ protected static bool CheckTimeout(ExecutionState executionState, bool thr #if WINDOWS_DESKTOP protected static bool CheckCancellation(ExecutionState executionState) { - if (executionState.CancelRequested) + lock (executionState.CancellationLockerObject) { - executionState.ExceptionRef = Exceptions.GenerateCancellationException(executionState.Cmd.CurrentResult, null); - } + if (executionState.CancelRequested) + { + executionState.ExceptionRef = Exceptions.GenerateCancellationException(executionState.Cmd.CurrentResult, null); + } - return executionState.CancelRequested; + return executionState.CancelRequested; + } } #endif diff --git a/Lib/Common/Core/Executor/RESTCommand.cs b/Lib/Common/Core/Executor/RESTCommand.cs index e7920d031..8a9896b2b 100644 --- a/Lib/Common/Core/Executor/RESTCommand.cs +++ b/Lib/Common/Core/Executor/RESTCommand.cs @@ -66,6 +66,9 @@ public RESTCommand(StorageCredentials credentials, StorageUri storageUri, UriQue // Stream to potentially copy response into public Stream DestinationStream = null; + // Stream to potentially copy error response into + public Stream ErrorStream = null; + // if true, the inStream will be set before processresponse is called. public bool RetrieveResponseStream = false; diff --git a/Lib/Common/Core/MultiBufferMemoryStream.cs b/Lib/Common/Core/MultiBufferMemoryStream.cs index b425fa3bf..53c4e3fcd 100644 --- a/Lib/Common/Core/MultiBufferMemoryStream.cs +++ b/Lib/Common/Core/MultiBufferMemoryStream.cs @@ -276,6 +276,7 @@ public override void SetLength(long value) { this.Reserve(value); this.length = value; + this.position = Math.Min(this.position, this.length); } /// diff --git a/Lib/Common/Core/SR.cs b/Lib/Common/Core/SR.cs index 69147e8ae..924162cc5 100644 --- a/Lib/Common/Core/SR.cs +++ b/Lib/Common/Core/SR.cs @@ -22,6 +22,7 @@ namespace Microsoft.WindowsAzure.Storage.Core /// internal class SR { + public const string AbsoluteAddressNotPermitted = "Address '{0}' is an absolute address. Only relative addresses are permitted."; public const string ArgumentEmptyError = "The argument must not be empty string."; public const string ArgumentOutOfRangeError = "The argument is out of range. Value passed: {0}"; public const string ArgumentTooLargeError = "The argument '{0}' is larger than maximum of '{1}'"; @@ -132,6 +133,7 @@ internal class SR public const string TraceAbortError = "Could not abort pending request because of {0}."; public const string TraceAbortRetry = "Aborting pending retry due to user request."; public const string TraceDownload = "Downloading response body."; + public const string TraceDownloadError = "Downloading error response body."; public const string TraceRetryInfo = "The extended retry policy set the next location to {0} and updated the location mode to {1}."; public const string TraceGenericError = "Exception thrown during the operation: {0}."; public const string TraceGetResponse = "Waiting for response."; diff --git a/Lib/Common/Core/Util/NavigationHelper.cs b/Lib/Common/Core/Util/NavigationHelper.cs index a0b013291..7794ff3f0 100644 --- a/Lib/Common/Core/Util/NavigationHelper.cs +++ b/Lib/Common/Core/Util/NavigationHelper.cs @@ -141,7 +141,12 @@ internal static string GetParentName(StorageUri blobAddress, string delimiter, b string containerName; StorageUri containerUri; - GetContainerNameAndAddress(blobAddress, usePathStyleUris, out containerName, out containerUri); + bool explicitRoot = GetContainerNameAndAddress(blobAddress, usePathStyleUris, out containerName, out containerUri); + if (!explicitRoot) + { + return null; + } + containerName += NavigationHelper.Slash; // Get the blob path as the rest of the Uri @@ -170,7 +175,7 @@ internal static string GetParentName(StorageUri blobAddress, string delimiter, b { // Case 2 // // Parent of a folder is container - parentName = null; + parentName = string.Empty; } else { @@ -179,34 +184,12 @@ internal static string GetParentName(StorageUri blobAddress, string delimiter, b parentName = Uri.UnescapeDataString(blobPath.Substring(0, parentLength + delimiter.Length)); if (parentName == containerName) { - parentName = null; + parentName = string.Empty; } } } - return parentName; - } - - /// - /// Retrieves the parent address for a blob Uri. - /// - /// The blob address. - /// The delimiter. - /// If set to true use path style Uris. - /// The address of the parent. - /// - /// GetParentName(new Uri("http://test.blob.core.windows.net/mycontainer/myfolder/myblob", null)) - /// will return "http://test.blob.core.windows.net/mycontainer/myfolder/" - /// - internal static StorageUri GetParentAddress(StorageUri blobAddress, string delimiter, bool? usePathStyleUris) - { - string parentName = GetParentName(blobAddress, delimiter, usePathStyleUris); - if (parentName == null) - { - return null; - } - - return NavigationHelper.AppendPathToUri(NavigationHelper.GetServiceClientBaseAddress(blobAddress, usePathStyleUris), parentName); + return string.IsNullOrEmpty(parentName) ? parentName : parentName.Substring(containerName.Length); } /// @@ -272,83 +255,83 @@ internal static Uri GetServiceClientBaseAddress(Uri addressUri, bool? usePathSty /// Appends a path to a list of URIs correctly using "/" as separator. /// /// The base URI. - /// The relative or absolute URI. + /// The relative or absolute URI. /// The list of appended URIs. - internal static StorageUri AppendPathToUri(StorageUri uriList, string relativeOrAbsoluteUri) + internal static StorageUri AppendPathToUri(StorageUri uriList, string relativeUri) { - return AppendPathToUri(uriList, relativeOrAbsoluteUri, NavigationHelper.Slash); + return AppendPathToUri(uriList, relativeUri, NavigationHelper.Slash); } /// /// Appends a path to a list of URIs correctly using "/" as separator. /// /// The base URI. - /// The relative or absolute URI. + /// The relative or absolute URI. /// The separator. /// The list of appended URIs. - internal static StorageUri AppendPathToUri(StorageUri uriList, string relativeOrAbsoluteUri, string sep) + internal static StorageUri AppendPathToUri(StorageUri uriList, string relativeUri, string sep) { return new StorageUri( - AppendPathToSingleUri(uriList.PrimaryUri, relativeOrAbsoluteUri, sep), - AppendPathToSingleUri(uriList.SecondaryUri, relativeOrAbsoluteUri, sep)); + AppendPathToSingleUri(uriList.PrimaryUri, relativeUri, sep), + AppendPathToSingleUri(uriList.SecondaryUri, relativeUri, sep)); } /// /// Append a relative path to a URI, handling trailing slashes appropriately. /// /// The base URI. - /// The relative or absolute URI. + /// The relative or absolute URI. /// The appended Uri. - internal static Uri AppendPathToSingleUri(Uri uri, string relativeOrAbsoluteUri) + internal static Uri AppendPathToSingleUri(Uri uri, string relativeUri) { - return AppendPathToSingleUri(uri, relativeOrAbsoluteUri, NavigationHelper.Slash); + return AppendPathToSingleUri(uri, relativeUri, NavigationHelper.Slash); } /// /// Append a relative path to a URI, handling trailing slashes appropriately. /// /// The base URI. - /// The relative or absolute URI. + /// The relative or absolute URI. /// The separator. /// The appended Uri. - internal static Uri AppendPathToSingleUri(Uri uri, string relativeOrAbsoluteUri, string sep) + internal static Uri AppendPathToSingleUri(Uri uri, string relativeUri, string sep) { - if (uri == null) + if (uri == null || relativeUri.Length == 0) { - return null; + return uri; } - Uri relativeUri; + Uri absoluteUri; // Because of URI's Scheme, URI.TryCreate() can't differentiate a string with colon from an absolute URI. // A workaround is added here to verify if a given string is an absolute URI. - if (Uri.TryCreate(relativeOrAbsoluteUri, UriKind.Absolute, out relativeUri) && (relativeUri.Scheme == "http" || relativeUri.Scheme == "https")) + if (Uri.TryCreate(relativeUri, UriKind.Absolute, out absoluteUri) && (string.CompareOrdinal(absoluteUri.Scheme, "http") == 0 || string.CompareOrdinal(absoluteUri.Scheme, "https") == 0)) { // Handle case if relPath is an absolute Uri - if (uri.IsBaseOf(relativeUri)) + if (uri.IsBaseOf(absoluteUri)) { - return relativeUri; + return absoluteUri; } else { // Happens when using fiddler, DNS aliases, or potentially NATs - Uri absoluteUri = new Uri(relativeOrAbsoluteUri); + absoluteUri = new Uri(relativeUri); return new Uri(uri, absoluteUri.AbsolutePath); } } sep = Uri.EscapeUriString(sep); - relativeOrAbsoluteUri = Uri.EscapeUriString(relativeOrAbsoluteUri); + relativeUri = Uri.EscapeUriString(relativeUri); UriBuilder ub = new UriBuilder(uri); string appendString = null; if (ub.Path.EndsWith(sep, StringComparison.Ordinal)) { - appendString = relativeOrAbsoluteUri; + appendString = relativeUri; } else { - appendString = sep + relativeOrAbsoluteUri; + appendString = sep + relativeUri; } ub.Path += appendString; @@ -418,10 +401,14 @@ internal static string GetTableNameFromUri(Uri uri, bool? usePathStyleUris) /// True to use path style Uris. /// Name of the container. /// The container URI. - private static void GetContainerNameAndAddress(StorageUri blobAddress, bool? usePathStyleUris, out string containerName, out StorageUri containerUri) + /// true when the container is an explicit container. false, otherwise. + private static bool GetContainerNameAndAddress(StorageUri blobAddress, bool? usePathStyleUris, out string containerName, out StorageUri containerUri) { - containerName = GetContainerName(blobAddress.PrimaryUri, usePathStyleUris); + string blobName; + bool explicitCont = GetContainerNameAndBlobName(blobAddress.PrimaryUri, usePathStyleUris, out containerName, out blobName); containerUri = NavigationHelper.AppendPathToUri(GetServiceClientBaseAddress(blobAddress, usePathStyleUris), containerName); + + return explicitCont; } /// @@ -431,7 +418,8 @@ private static void GetContainerNameAndAddress(StorageUri blobAddress, bool? use /// If set to true use path style Uris. /// The resulting container name. /// The resulting blob name. - private static void GetContainerNameAndBlobName(Uri blobAddress, bool? usePathStyleUris, out string containerName, out string blobName) + /// A bool representing whether the blob is in an explicit container or not. + private static bool GetContainerNameAndBlobName(Uri blobAddress, bool? usePathStyleUris, out string containerName, out string blobName) { CommonUtility.AssertNotNull("blobAddress", blobAddress); @@ -476,7 +464,10 @@ private static void GetContainerNameAndBlobName(Uri blobAddress, bool? usePathSt string[] blobNameSegments = new string[addressParts.Length - firstBlobIndex]; Array.Copy(addressParts, firstBlobIndex, blobNameSegments, 0, blobNameSegments.Length); blobName = string.Concat(blobNameSegments); + return true; } + + return false; } /// diff --git a/Lib/Common/Shared/Protocol/Constants.cs b/Lib/Common/Shared/Protocol/Constants.cs index 98424824e..a078d4773 100644 --- a/Lib/Common/Shared/Protocol/Constants.cs +++ b/Lib/Common/Shared/Protocol/Constants.cs @@ -709,7 +709,7 @@ static HeaderConstants() /// /// Specifies the value to use for UserAgent header. /// - public const string UserAgentProductVersion = "3.0.2"; + public const string UserAgentProductVersion = "3.0.3"; /// /// Master Windows Azure Storage header prefix. diff --git a/Lib/Common/StorageException.cs b/Lib/Common/StorageException.cs index 85db86597..41488ad89 100644 --- a/Lib/Common/StorageException.cs +++ b/Lib/Common/StorageException.cs @@ -27,6 +27,7 @@ namespace Microsoft.WindowsAzure.Storage using System.Text; #if WINDOWS_DESKTOP + using Microsoft.WindowsAzure.Storage.Core; using System.Runtime.Serialization; using System.IO; using System.Collections.Generic; @@ -137,9 +138,6 @@ public StorageException(RequestResult res, string message, Exception inner) /// The request result. /// The storage exception. /// An exception of type . - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "General Exception wrapped as a StorageException.")] - [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Code clarity.")] - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "req", Justification = "Reviewed : req is allowed.")] public static StorageException TranslateException(Exception ex, RequestResult reqResult) { return TranslateException(ex, reqResult, null); @@ -152,9 +150,91 @@ public static StorageException TranslateException(Exception ex, RequestResult re /// The request result. /// The delegate used to parse the error to get extended error information. /// The storage exception. - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "General Exception wrapped as a StorageException.")] - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "req", Justification = "Reviewed : req is allowed.")] public static StorageException TranslateException(Exception ex, RequestResult reqResult, Func parseError) + { + StorageException storageException; + if ((storageException = CoreTranslate(ex, reqResult, ref parseError)) != null) + { + return storageException; + } + + WebException we = ex as WebException; + if (we != null) + { + try + { + HttpWebResponse response = we.Response as HttpWebResponse; + if (response != null) + { + StorageException.PopulateRequestResult(reqResult, response); +#if WINDOWS_RT + reqResult.ExtendedErrorInformation = StorageExtendedErrorInformation.ReadFromStream(response.GetResponseStream().AsInputStream()); +#else + reqResult.ExtendedErrorInformation = parseError(response.GetResponseStream()); +#endif + } + } + catch (Exception) + { + // no op + } + } + + // Just wrap in StorageException + return new StorageException(reqResult, ex.Message, ex); + } + + /// + /// Translates the specified exception into a storage exception. + /// + /// The exception to translate. + /// The request result. + /// The delegate used to parse the error to get extended error information. + /// The error stream that contains the error information. + /// The storage exception. + internal static StorageException TranslateExceptionWithPreBufferedStream(Exception ex, RequestResult reqResult, Func parseError, Stream responseStream) + { + StorageException storageException; + if ((storageException = CoreTranslate(ex, reqResult, ref parseError)) != null) + { + return storageException; + } + + WebException we = ex as WebException; + if (we != null) + { + try + { + HttpWebResponse response = we.Response as HttpWebResponse; + if (response != null) + { + PopulateRequestResult(reqResult, response); + +#if WINDOWS_RT + reqResult.ExtendedErrorInformation = StorageExtendedErrorInformation.ReadFromStream(responseStream); +#else + reqResult.ExtendedErrorInformation = parseError(responseStream); +#endif + } + } + catch (Exception) + { + // no op + } + } + + // Just wrap in StorageException + return new StorageException(reqResult, ex.Message, ex); + } + + /// + /// Tries to translate the specified exception into a storage exception. + /// + /// The exception to translate. + /// The request result. + /// The delegate used to parse the error to get extended error information. + /// The storage exception or null. + private static StorageException CoreTranslate(Exception ex, RequestResult reqResult, ref Func parseError) { CommonUtility.AssertNotNull("reqResult", reqResult); CommonUtility.AssertNotNull("ex", ex); @@ -203,42 +283,8 @@ public static StorageException TranslateException(Exception ex, RequestResult re } } #endif - - WebException we = ex as WebException; - if (we != null) - { - try - { - HttpWebResponse response = we.Response as HttpWebResponse; - if (response != null) - { - reqResult.HttpStatusMessage = response.StatusDescription; - reqResult.HttpStatusCode = (int)response.StatusCode; - if (response.Headers != null) - { -#if WINDOWS_DESKTOP - reqResult.ServiceRequestID = HttpWebUtility.TryGetHeader(response, Constants.HeaderConstants.RequestIdHeader, null); - reqResult.ContentMd5 = HttpWebUtility.TryGetHeader(response, "Content-MD5", null); - string tempDate = HttpWebUtility.TryGetHeader(response, "Date", null); - reqResult.RequestDate = string.IsNullOrEmpty(tempDate) ? DateTime.Now.ToString("R", CultureInfo.InvariantCulture) : tempDate; - reqResult.Etag = response.Headers[HttpResponseHeader.ETag]; -#endif - } -#if WINDOWS_RT - reqResult.ExtendedErrorInformation = StorageExtendedErrorInformation.ReadFromStream(response.GetResponseStream().AsInputStream()); -#else - reqResult.ExtendedErrorInformation = parseError(response.GetResponseStream()); -#endif - } - } - catch (Exception) - { - // no op - } - } - - // Not WebException, just wrap in StorageException - return new StorageException(reqResult, ex.Message, ex); + // return null and check in the caller + return null; } #if WINDOWS_DESKTOP && !WINDOWS_PHONE @@ -249,8 +295,6 @@ public static StorageException TranslateException(Exception ex, RequestResult re /// The request result. /// The delegate used to parse the error to get extended error information. /// The storage exception. - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "General Exception wrapped as a StorageException.")] - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "req", Justification = "Reviewed : req is allowed.")] internal static StorageException TranslateDataServiceException(Exception ex, RequestResult reqResult, Func, StorageExtendedErrorInformation> parseError) { CommonUtility.AssertNotNull("reqResult", reqResult); @@ -290,6 +334,26 @@ internal static StorageException TranslateDataServiceException(Exception ex, Req return new StorageException(reqResult, ex.Message, ex); } #endif + /// + /// Populate the RequestResult. + /// + /// The request result. + /// The web response. + private static void PopulateRequestResult(RequestResult reqResult, HttpWebResponse response) + { + reqResult.HttpStatusMessage = response.StatusDescription; + reqResult.HttpStatusCode = (int)response.StatusCode; + if (response.Headers != null) + { +#if WINDOWS_DESKTOP + reqResult.ServiceRequestID = HttpWebUtility.TryGetHeader(response, Constants.HeaderConstants.RequestIdHeader, null); + reqResult.ContentMd5 = HttpWebUtility.TryGetHeader(response, "Content-MD5", null); + string tempDate = HttpWebUtility.TryGetHeader(response, "Date", null); + reqResult.RequestDate = string.IsNullOrEmpty(tempDate) ? DateTime.Now.ToString("R", CultureInfo.InvariantCulture) : tempDate; + reqResult.Etag = response.Headers[HttpResponseHeader.ETag]; +#endif + } + } /// /// Represents an exception thrown by the Windows Azure storage client library. diff --git a/Lib/Common/Table/CloudTableClient.Common.cs b/Lib/Common/Table/CloudTableClient.Common.cs index 5c12f17b3..a2c69c5aa 100644 --- a/Lib/Common/Table/CloudTableClient.Common.cs +++ b/Lib/Common/Table/CloudTableClient.Common.cs @@ -50,6 +50,8 @@ public sealed partial class CloudTableClient private AuthenticationScheme authenticationScheme; + private string accountName; + /// /// Initializes a new instance of the class using the specified Table service endpoint /// and anonymous credentials. @@ -90,12 +92,17 @@ public CloudTableClient(StorageUri storageUri, StorageCredentials credentials) #endif { this.StorageUri = storageUri; - this.Credentials = credentials ?? new StorageCredentials(); + this.Credentials = credentials ?? new StorageCredentials(); this.RetryPolicy = new ExponentialRetry(); this.LocationMode = LocationMode.PrimaryOnly; this.ServerTimeout = Constants.DefaultServerSideTimeout; this.AuthenticationScheme = AuthenticationScheme.SharedKey; this.UsePathStyleUris = CommonUtility.UsePathStyleAddressing(this.BaseUri); + + if (!this.Credentials.IsSharedKey) + { + this.AccountName = NavigationHelper.GetAccountNameFromUri(this.BaseUri, this.UsePathStyleUris); + } } /// @@ -213,6 +220,23 @@ public TablePayloadFormat PayloadFormat /// Is true if use path style URIs; otherwise, false. internal bool UsePathStyleUris { get; private set; } + /// + /// Gets the associated account name for the client. + /// + /// The account name. + internal string AccountName + { + get + { + return this.accountName ?? this.Credentials.AccountName; + } + + private set + { + this.accountName = value; + } + } + /// /// Gets a reference to the specified table. /// diff --git a/Lib/WindowsAzure.Storage-Preview.nuspec b/Lib/WindowsAzure.Storage-Preview.nuspec index 5ba7db5a3..d33f35252 100644 --- a/Lib/WindowsAzure.Storage-Preview.nuspec +++ b/Lib/WindowsAzure.Storage-Preview.nuspec @@ -2,7 +2,7 @@ WindowsAzure.Storage-Preview - 3.0.2.0-preview + 3.0.3.0-preview Windows Azure Storage Microsoft Microsoft diff --git a/Lib/WindowsDesktop/GlobalSuppressions.cs b/Lib/WindowsDesktop/GlobalSuppressions.cs index c992d5032..f761c6f8c 100644 --- a/Lib/WindowsDesktop/GlobalSuppressions.cs +++ b/Lib/WindowsDesktop/GlobalSuppressions.cs @@ -288,6 +288,14 @@ [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.StorageExtendedErrorInformation.#ReadFromStreamUsingODataLib(System.IO.Stream,System.Net.HttpWebResponse,System.String)", Justification = "Reviewed")] [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.StorageExtendedErrorInformation.#ReadDataServiceResponseFromStream(System.IO.Stream,System.Collections.Generic.Dictionary`2,System.String)", Justification = "Reviewed")] [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.StorageExtendedErrorInformation.#ReadAndParseExtendedError(Microsoft.Data.OData.IODataResponseMessage)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Executor.ExecutorBase.#BeginTranslateExceptionBasedOnParseError(System.Exception,Microsoft.WindowsAzure.Storage.RequestResult,System.Net.HttpWebResponse,System.Func`4,System.AsyncCallback,System.Object)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.Protocol.TableUtilities.#TranslateDataServiceException(System.Exception,Microsoft.WindowsAzure.Storage.RequestResult,System.Func`3,Microsoft.WindowsAzure.Storage.StorageExtendedErrorInformation>)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Executor.Executor.#BeginTranslateExceptionBasedOnParseError(System.Exception,Microsoft.WindowsAzure.Storage.RequestResult,System.Net.HttpWebResponse,System.Func`4,System.AsyncCallback,System.Object)", Justification = "General exception is wrapped in StorageException")] +[assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Executor.Executor.#ClearStateAndHandleRetryAsync`1(Microsoft.WindowsAzure.Storage.Core.Executor.ExecutionState`1)", Justification = "General exception is wrapped in StorageException")] +[assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.StorageException.#BeginTranslateException(System.Exception,Microsoft.WindowsAzure.Storage.RequestResult,System.Func`2,System.AsyncCallback,System.Object)", Justification = "General exception is wrapped in StorageException")] +[assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.StorageException.#TranslateException(System.Exception,Microsoft.WindowsAzure.Storage.RequestResult,System.Func`2)", Justification = "General exception is wrapped in StorageException")] +[assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.StorageException.#TranslateExceptionAsync(System.Exception,Microsoft.WindowsAzure.Storage.RequestResult,System.Func`2,System.IO.Stream)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.StorageException.#TranslateExceptionWithPreBufferedStream(System.Exception,Microsoft.WindowsAzure.Storage.RequestResult,System.Func`2,System.IO.Stream)", 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")] @@ -300,6 +308,7 @@ // 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.")] // 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")] @@ -316,6 +325,7 @@ [assembly: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.Protocol.ListBlobsResponse+d__0.#MoveNext()", Justification = "Reviewed")] [assembly: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Executor.TableExecutor.#InitRequest`2(Microsoft.WindowsAzure.Storage.Core.Executor.ExecutionState`1)", Justification = "Reviewed")] [assembly: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Executor.TableExecutor.#ExecuteSync`2(Microsoft.WindowsAzure.Storage.Core.Executor.TableCommand`2,Microsoft.WindowsAzure.Storage.RetryPolicies.IRetryPolicy,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Executor.Executor.#EndOperation`1(Microsoft.WindowsAzure.Storage.Core.Executor.ExecutionState`1)", Justification = "Reviewed")] // CA1505 [assembly: SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.Queryable.ReflectionUtil.#.cctor()", Justification = "Reviewed")] @@ -343,6 +353,7 @@ [assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableQuery`1.#QueryImpl`2(Microsoft.WindowsAzure.Storage.Table.TableQuery`1,Microsoft.WindowsAzure.Storage.Table.TableContinuationToken,Microsoft.WindowsAzure.Storage.Table.CloudTableClient,Microsoft.WindowsAzure.Storage.Table.CloudTable,Microsoft.WindowsAzure.Storage.Table.EntityResolver`1,Microsoft.WindowsAzure.Storage.Table.TableRequestOptions)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableQuery.#QueryImpl(Microsoft.WindowsAzure.Storage.Table.TableQuery,Microsoft.WindowsAzure.Storage.Table.TableContinuationToken,Microsoft.WindowsAzure.Storage.Table.CloudTableClient,Microsoft.WindowsAzure.Storage.Table.CloudTable,Microsoft.WindowsAzure.Storage.Table.TableRequestOptions)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableQuery.#QueryImpl`1(Microsoft.WindowsAzure.Storage.Table.TableQuery,Microsoft.WindowsAzure.Storage.Table.TableContinuationToken,Microsoft.WindowsAzure.Storage.Table.CloudTableClient,Microsoft.WindowsAzure.Storage.Table.CloudTable,Microsoft.WindowsAzure.Storage.Table.EntityResolver`1,Microsoft.WindowsAzure.Storage.Table.TableRequestOptions)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Executor.Executor.#ClearStateAndHandleRetryAsync`1(Microsoft.WindowsAzure.Storage.Core.Executor.ExecutionState`1)", Justification = "Reviewed.")] // CA1702 [assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "etag", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.AccessCondition.#GenerateIfNoneMatchCondition(System.String)", Justification = "Reviewed")] @@ -354,6 +365,10 @@ [assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Etag", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.RequestResult.#Etag", Justification = "Reviewed")] [assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Etag", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableResult.#Etag", Justification = "Reviewed")] +// CA1704 +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "req", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.StorageException.#TranslateException(System.Exception,Microsoft.WindowsAzure.Storage.RequestResult)", Justification = "req is allowed")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "req", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.StorageException.#TranslateException(System.Exception,Microsoft.WindowsAzure.Storage.RequestResult,System.Func`2)", Justification = "req is allowed")] + // CA1710 [assembly: SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Scope = "type", Target = "Microsoft.WindowsAzure.Storage.Table.TableQuery`1", Justification = "Reviewed")] @@ -365,6 +380,8 @@ [assembly: SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.StorageException.#TranslateDataServiceException(System.Exception,Microsoft.WindowsAzure.Storage.RequestResult,System.Func`3,Microsoft.WindowsAzure.Storage.StorageExtendedErrorInformation>)", Justification = "Code clarity")] [assembly: SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.EntityProperty.#CreateEntityPropertyFromObject(System.Object,System.Boolean)", Justification = "Code clarity")] [assembly: SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.StorageException.#TranslateException(System.Exception,Microsoft.WindowsAzure.Storage.RequestResult,System.Func`2)", Justification = "Code clarity")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.StorageException.#BeginTranslateException(System.Exception,Microsoft.WindowsAzure.Storage.RequestResult,System.Func`2,System.AsyncCallback,System.Object)", Justification = "Code clarity")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.StorageException.#CoreTranslate(System.Exception,Microsoft.WindowsAzure.Storage.RequestResult,System.Func`2&)", Justification = "Code clarity")] // CA1801 [assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "columns", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableQuery.#Project`1(!!0,System.String[])", Justification = "Reviewed")] @@ -467,6 +484,8 @@ [assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.Protocol.TableOperationHttpWebRequestFactory.#BuildRequestForTableBatchOperation(System.Uri,Microsoft.WindowsAzure.Storage.Core.UriQueryBuilder,Microsoft.WindowsAzure.Storage.IBufferManager,System.Nullable`1,System.Uri,System.String,Microsoft.WindowsAzure.Storage.Table.TableBatchOperation,Microsoft.WindowsAzure.Storage.OperationContext,Microsoft.WindowsAzure.Storage.Table.PayloadFormat,System.String)", Justification = "Reviewed")] [assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.Protocol.TableOperationHttpWebRequestFactory.#BuildRequestForTableOperation(System.Uri,Microsoft.WindowsAzure.Storage.Core.UriQueryBuilder,Microsoft.WindowsAzure.Storage.IBufferManager,System.Nullable`1,Microsoft.WindowsAzure.Storage.Table.TableOperation,Microsoft.WindowsAzure.Storage.OperationContext,Microsoft.WindowsAzure.Storage.Table.TablePayloadFormat,System.String)", Justification = "Reviewed")] [assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.Protocol.TableOperationHttpWebRequestFactory.#BuildRequestForTableBatchOperation(System.Uri,Microsoft.WindowsAzure.Storage.Core.UriQueryBuilder,Microsoft.WindowsAzure.Storage.IBufferManager,System.Nullable`1,System.Uri,System.String,Microsoft.WindowsAzure.Storage.Table.TableBatchOperation,Microsoft.WindowsAzure.Storage.OperationContext,Microsoft.WindowsAzure.Storage.Table.TablePayloadFormat,System.String)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.StorageExtendedErrorInformation.#BeginCopyFromStream(System.IO.Stream,System.AsyncCallback,System.Object)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Executor.Executor.#ClearStateAndHandleRetryAsync`1(Microsoft.WindowsAzure.Storage.Core.Executor.ExecutionState`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")] @@ -492,4 +511,4 @@ [assembly: SuppressMessage("Microsoft.Security.Cryptography", "CA5350:MD5CannotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Util.NativeMD5.#HashCore(System.Byte[],System.Int32,System.Int32)", Justification = "Reviewed")] [assembly: SuppressMessage("Microsoft.Security.Cryptography", "CA5350:MD5CannotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Util.MD5Wrapper.#.ctor()", Justification = "Reviewed")] [assembly: SuppressMessage("Microsoft.Security.Cryptography", "CA5350:MD5CannotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Util.NativeMD5.#.ctor()", Justification = "Reviewed")] -[assembly: SuppressMessage("Microsoft.Security.Cryptography", "CA5350:MD5CannotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Util.NativeMD5.#Initialize()", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Security.Cryptography", "CA5350:MD5CannotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Util.NativeMD5.#Initialize()", Justification = "Reviewed")] \ No newline at end of file diff --git a/Lib/WindowsDesktop/Properties/AssemblyInfo.cs b/Lib/WindowsDesktop/Properties/AssemblyInfo.cs index cf4fe495d..3e59da040 100644 --- a/Lib/WindowsDesktop/Properties/AssemblyInfo.cs +++ b/Lib/WindowsDesktop/Properties/AssemblyInfo.cs @@ -34,8 +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("3.0.2.0")] -[assembly: AssemblyFileVersion("3.0.2.0")] +[assembly: AssemblyVersion("3.0.3.0")] +[assembly: AssemblyFileVersion("3.0.3.0")] #if SIGN [assembly: InternalsVisibleTo( diff --git a/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec b/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec index c1a123946..7b76dd4ac 100644 --- a/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec +++ b/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec @@ -2,7 +2,7 @@ WindowsAzure.Storage - 3.0.2.0 + 3.0.3.0 Windows Azure Storage Microsoft Microsoft diff --git a/Lib/WindowsPhone/Properties/AssemblyInfo.cs b/Lib/WindowsPhone/Properties/AssemblyInfo.cs index 7a22fda23..25919c163 100644 --- a/Lib/WindowsPhone/Properties/AssemblyInfo.cs +++ b/Lib/WindowsPhone/Properties/AssemblyInfo.cs @@ -32,8 +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("3.0.2.0")] -[assembly: AssemblyFileVersion("3.0.2.0")] +[assembly: AssemblyVersion("3.0.3.0")] +[assembly: AssemblyFileVersion("3.0.3.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] #if SIGN diff --git a/Lib/WindowsPhone/Settings.StyleCop b/Lib/WindowsPhone/Settings.StyleCop index a693cb5df..d1495b163 100644 --- a/Lib/WindowsPhone/Settings.StyleCop +++ b/Lib/WindowsPhone/Settings.StyleCop @@ -16,6 +16,7 @@ Fisma func impl + Json Md myblob mycontainer @@ -235,7 +236,7 @@ False - + diff --git a/Lib/WindowsRuntime/Core/Executor/Executor.cs b/Lib/WindowsRuntime/Core/Executor/Executor.cs index 6714bc56e..4b810f80d 100644 --- a/Lib/WindowsRuntime/Core/Executor/Executor.cs +++ b/Lib/WindowsRuntime/Core/Executor/Executor.cs @@ -130,6 +130,7 @@ private async static Task ExecuteAsyncInternal(RESTCommand cmd, IRetryP // Since HttpClient wont throw for non success, manually check and populate an exception if (!executionState.Resp.IsSuccessStatusCode) { + // At this point, don't try to read the stream to parse the error executionState.ExceptionRef = await Exceptions.PopulateStorageExceptionFromHttpResponseMessage(executionState.Resp, executionState.Cmd.CurrentResult); } @@ -140,10 +141,19 @@ private async static Task ExecuteAsyncInternal(RESTCommand cmd, IRetryP if (cmd.PreProcessResponse != null) { executionState.CurrentOperation = ExecutorOperation.PreProcess; - executionState.Result = cmd.PreProcessResponse(cmd, executionState.Resp, executionState.ExceptionRef, executionState.OperationContext); - // clear exception - executionState.ExceptionRef = null; + try + { + executionState.Result = cmd.PreProcessResponse(cmd, executionState.Resp, executionState.ExceptionRef, executionState.OperationContext); + + // clear exception + executionState.ExceptionRef = null; + } + catch (Exception ex) + { + executionState.ExceptionRef = ex; + } + Logger.LogInformational(executionState.OperationContext, SR.TracePreProcessDone); } @@ -151,28 +161,54 @@ private async static Task ExecuteAsyncInternal(RESTCommand cmd, IRetryP executionState.CurrentOperation = ExecutorOperation.GetResponseStream; cmd.ResponseStream = await executionState.Resp.Content.ReadAsStreamAsync(); - if (!cmd.RetrieveResponseStream) + // The stream is now available in ResponseStream. Use the stream to parse out the response or error + if (executionState.ExceptionRef != null) { - cmd.DestinationStream = Stream.Null; - } - - if (cmd.DestinationStream != null) - { - if (cmd.StreamCopyState == null) - { - cmd.StreamCopyState = new StreamDescriptor(); - } + executionState.CurrentOperation = ExecutorOperation.BeginDownloadResponse; + Logger.LogInformational(executionState.OperationContext, SR.TraceDownloadError); try { - executionState.CurrentOperation = ExecutorOperation.BeginDownloadResponse; - Logger.LogInformational(executionState.OperationContext, SR.TraceDownload); - await cmd.ResponseStream.WriteToAsync(cmd.DestinationStream, null /* copyLength */, null /* maxLength */, cmd.CalculateMd5ForResponseStream, executionState, cmd.StreamCopyState, token); + cmd.ErrorStream = new MemoryStream(); + await cmd.ResponseStream.WriteToAsync(cmd.ErrorStream, null /* copyLength */, null /* maxLength */, false, executionState, new StreamDescriptor(), token); + cmd.ErrorStream.Seek(0, SeekOrigin.Begin); + executionState.ExceptionRef = StorageException.TranslateExceptionWithPreBufferedStream(executionState.ExceptionRef, executionState.Cmd.CurrentResult, null /* parseError */, cmd.ErrorStream); + throw executionState.ExceptionRef; } finally { cmd.ResponseStream.Dispose(); cmd.ResponseStream = null; + + cmd.ErrorStream.Dispose(); + cmd.ErrorStream = null; + } + } + else + { + if (!cmd.RetrieveResponseStream) + { + cmd.DestinationStream = Stream.Null; + } + + if (cmd.DestinationStream != null) + { + if (cmd.StreamCopyState == null) + { + cmd.StreamCopyState = new StreamDescriptor(); + } + + try + { + executionState.CurrentOperation = ExecutorOperation.BeginDownloadResponse; + Logger.LogInformational(executionState.OperationContext, SR.TraceDownload); + await cmd.ResponseStream.WriteToAsync(cmd.DestinationStream, null /* copyLength */, null /* maxLength */, cmd.CalculateMd5ForResponseStream, executionState, cmd.StreamCopyState, token); + } + finally + { + cmd.ResponseStream.Dispose(); + cmd.ResponseStream = null; + } } } @@ -213,7 +249,8 @@ private async static Task ExecuteAsyncInternal(RESTCommand cmd, IRetryP IExtendedRetryPolicy extendedRetryPolicy = executionState.RetryPolicy as IExtendedRetryPolicy; if (extendedRetryPolicy != null) { - RetryContext retryContext = new RetryContext(executionState.RetryCount++, + RetryContext retryContext = new RetryContext( + executionState.RetryCount++, cmd.CurrentResult, executionState.CurrentLocation, cmd.LocationMode); diff --git a/Lib/WindowsRuntime/Properties/AssemblyInfo.cs b/Lib/WindowsRuntime/Properties/AssemblyInfo.cs index 7a5c03f44..05464c6c4 100644 --- a/Lib/WindowsRuntime/Properties/AssemblyInfo.cs +++ b/Lib/WindowsRuntime/Properties/AssemblyInfo.cs @@ -25,8 +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("3.0.2.0")] -[assembly: AssemblyFileVersion("3.0.2.0")] +[assembly: AssemblyVersion("3.0.3.0")] +[assembly: AssemblyFileVersion("3.0.3.0")] [assembly: ComVisible(false)] #if SIGN diff --git a/Lib/WindowsRuntime/Settings.StyleCop b/Lib/WindowsRuntime/Settings.StyleCop index 50892cf44..d8d7fad5b 100644 --- a/Lib/WindowsRuntime/Settings.StyleCop +++ b/Lib/WindowsRuntime/Settings.StyleCop @@ -13,6 +13,7 @@ ETag Fisma func + Json Md myblob mycontainer diff --git a/Lib/WindowsRuntime/Table/Protocol/TableOperationHttpResponseParsers.cs b/Lib/WindowsRuntime/Table/Protocol/TableOperationHttpResponseParsers.cs index 398d819d7..c0370cb88 100644 --- a/Lib/WindowsRuntime/Table/Protocol/TableOperationHttpResponseParsers.cs +++ b/Lib/WindowsRuntime/Table/Protocol/TableOperationHttpResponseParsers.cs @@ -45,20 +45,20 @@ internal static TableResult TableOperationPreProcess(TableResult result, Tabl { if (ex != null) { - throw StorageException.TranslateException(ex, cmd.CurrentResult, null); + throw ex; } else if (operation.OperationType == TableOperationType.Insert) { if (resp.StatusCode != HttpStatusCode.Created) { - throw StorageException.TranslateException(ex, cmd.CurrentResult, null); + throw ex; } } else { if (resp.StatusCode != HttpStatusCode.NoContent) { - throw StorageException.TranslateException(ex, cmd.CurrentResult, null); + throw ex; } } } diff --git a/Lib/WindowsRuntimeTable/Properties/AssemblyInfo.cs b/Lib/WindowsRuntimeTable/Properties/AssemblyInfo.cs index fbb79a5c2..0c12cdf4e 100644 --- a/Lib/WindowsRuntimeTable/Properties/AssemblyInfo.cs +++ b/Lib/WindowsRuntimeTable/Properties/AssemblyInfo.cs @@ -25,8 +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("3.0.2.0")] -[assembly: AssemblyFileVersion("3.0.2.0")] +[assembly: AssemblyVersion("3.0.3.0")] +[assembly: AssemblyFileVersion("3.0.3.0")] [assembly: ComVisible(false)] #if SIGN diff --git a/Lib/WindowsRuntimeTable/WindowsAzure.Storage.Table-Preview.nuspec b/Lib/WindowsRuntimeTable/WindowsAzure.Storage.Table-Preview.nuspec index c2bb15fc5..869f80d1f 100644 --- a/Lib/WindowsRuntimeTable/WindowsAzure.Storage.Table-Preview.nuspec +++ b/Lib/WindowsRuntimeTable/WindowsAzure.Storage.Table-Preview.nuspec @@ -2,7 +2,7 @@ WindowsAzure.Storage.Table-Preview - 3.0.2.0-preview + 3.0.3.0-preview Windows Azure Storage Tables Extension for Windows Runtime Microsoft Microsoft @@ -16,7 +16,7 @@ Windows Azure Storage team's blog - http://blogs.msdn.com/b/windowsazurestorage/ A table extension library for Windows Runtime for working with Windows Azure Storage tables. Microsoft, Azure, Storage, Table, Scalable, winrt, windowsazureofficial - + diff --git a/Test/ClassLibraryCommon/Blob/CloudBlobDirectoryTest.cs b/Test/ClassLibraryCommon/Blob/CloudBlobDirectoryTest.cs index d1886e089..1805f7235 100644 --- a/Test/ClassLibraryCommon/Blob/CloudBlobDirectoryTest.cs +++ b/Test/ClassLibraryCommon/Blob/CloudBlobDirectoryTest.cs @@ -18,6 +18,8 @@ namespace Microsoft.WindowsAzure.Storage.Blob { using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.WindowsAzure.Storage.Core; + using Microsoft.WindowsAzure.Storage.Core.Util; using System; using System.Collections.Generic; using System.Linq; @@ -27,7 +29,7 @@ namespace Microsoft.WindowsAzure.Storage.Blob [TestClass] public class CloudBlobDirectoryTest : BlobTestBase { - string[] Delimiters = new string[] {"$", "@", "-", "%", "/", "|"}; + string[] Delimiters = new string[] { ":", "$", "@", "-", "%", "/", "|", "$$", "::", "//"}; private bool CloudBlobDirectorySetupWithDelimiter(CloudBlobContainer container, string delimiter = "/") { @@ -97,13 +99,34 @@ public void CloudBlobDirectoryGetParent() container.Create(); CloudPageBlob blob = container.GetPageBlobReference("Dir1" + delimiter + "Blob1"); blob.Create(0); - Assert.IsTrue(blob.Exists()); Assert.AreEqual("Dir1" + delimiter + "Blob1", blob.Name); + + // get the blob's parent CloudBlobDirectory parent = blob.Parent; Assert.AreEqual(parent.Prefix, "Dir1" + delimiter); - blob.Delete(); - } + // get container as parent + CloudBlobDirectory root = parent.Parent; + Assert.AreEqual(root.Prefix, ""); + + // make sure the parent of the container dir is null + CloudBlobDirectory empty = root.Parent; + Assert.IsNull(empty); + + // from container, get directory reference to container + root = container.GetDirectoryReference(""); + Assert.AreEqual("", root.Prefix); + Assert.AreEqual(container.Uri.AbsoluteUri, root.Uri.AbsoluteUri); + + List list = root.ListBlobs().ToList(); + Assert.AreEqual(1, list.Count); + + // make sure the parent of the container dir is null + empty = root.Parent; + Assert.IsNull(empty); + + blob.DeleteIfExists(); + } finally { container.DeleteIfExists(); @@ -223,7 +246,7 @@ public void CloudBlobDirectoryFlatListingAPM() list1.AddRange(result1.Results); token = result1.ContinuationToken; } - while(token!=null); + while (token != null); Assert.IsTrue(list1.Count == 3); @@ -255,7 +278,7 @@ public void CloudBlobDirectoryFlatListingAPM() Assert.IsTrue(item41.Uri.Equals(container.Uri + "/TopDir1" + delimiter + "MidDir2" + delimiter + "EndDir1" + delimiter + "EndBlob1")); IListBlobItem item42 = list2.ElementAt(1); - Assert.IsTrue(item42.Uri.Equals(container.Uri + "/TopDir1" + delimiter + "MidDir2" + delimiter + "EndDir2" + delimiter + "EndBlob2")); + Assert.IsTrue(item42.Uri.Equals(container.Uri + "/TopDir1" + delimiter + "MidDir2" + delimiter + "EndDir2" + delimiter + "EndBlob2")); } } } @@ -365,7 +388,7 @@ public void CloudBlobDirectoryFlatListingWithPrefix() BlobResultSegment result1 = directory.ListBlobsSegmented(token); token = result1.ContinuationToken; list1.AddRange(result1.Results); - } + } while (token != null); Assert.IsTrue(list1.Count == 3); @@ -387,7 +410,7 @@ public void CloudBlobDirectoryFlatListingWithPrefix() BlobResultSegment result2 = midDir2.ListBlobsSegmented(true, BlobListingDetails.None, null, token, null, null); token = result2.ContinuationToken; list2.AddRange(result2.Results); - } + } while (token != null); Assert.IsTrue(list2.Count == 2); @@ -455,7 +478,10 @@ public void CloudBlobDirectoryGetParentOnRoot() { CloudBlobDirectory root = container.GetDirectoryReference("TopDir1" + delimiter); CloudBlobDirectory parent = root.Parent; - Assert.IsNull(parent); + Assert.IsNotNull(parent); + + CloudBlobDirectory empty = parent.Parent; + Assert.IsNull(empty); } finally { @@ -601,6 +627,7 @@ public void CloudBlobDirectoryValidateInRootContainer() CloudBlobContainer container = client.GetContainerReference("$root"); CloudPageBlob pageBlob = container.GetPageBlobReference("Dir1" + delimiter + "Blob1"); + if (delimiter == "/") { TestHelper.ExpectedException( @@ -612,6 +639,7 @@ public void CloudBlobDirectoryValidateInRootContainer() else { CloudPageBlob blob = container.GetPageBlobReference("TopDir1" + delimiter + "MidDir1" + delimiter + "EndDir1" + delimiter + "EndBlob1"); + CloudBlobDirectory directory = blob.Parent; Assert.AreEqual(directory.Prefix, "TopDir1" + delimiter + "MidDir1" + delimiter + "EndDir1" + delimiter); Assert.AreEqual(directory.Uri, container.Uri + "/TopDir1" + delimiter + "MidDir1" + delimiter + "EndDir1" + delimiter); @@ -647,14 +675,14 @@ public void CloudBlobDirectoryDelimitersInARow() try { - CloudPageBlob blob = container.GetPageBlobReference(delimiter+delimiter+delimiter+"Blob1"); + CloudPageBlob blob = container.GetPageBlobReference(delimiter + delimiter + delimiter + "Blob1"); ////Traverse from leaf to root CloudBlobDirectory directory1 = blob.Parent; - Assert.AreEqual(directory1.Prefix, delimiter+delimiter+delimiter); + Assert.AreEqual(directory1.Prefix, delimiter + delimiter + delimiter); CloudBlobDirectory directory2 = directory1.Parent; - Assert.AreEqual(directory2.Prefix, delimiter+delimiter); + Assert.AreEqual(directory2.Prefix, delimiter + delimiter); CloudBlobDirectory directory3 = directory2.Parent; Assert.AreEqual(directory3.Prefix, delimiter); @@ -662,13 +690,13 @@ public void CloudBlobDirectoryDelimitersInARow() ////Traverse from root to leaf CloudBlobDirectory directory4 = container.GetDirectoryReference(delimiter); CloudBlobDirectory directory5 = directory4.GetSubdirectoryReference(delimiter); - Assert.AreEqual(directory5.Prefix, delimiter+delimiter); + Assert.AreEqual(directory5.Prefix, delimiter + delimiter); CloudBlobDirectory directory6 = directory5.GetSubdirectoryReference(delimiter); - Assert.AreEqual(directory6.Prefix, delimiter+delimiter+delimiter); + Assert.AreEqual(directory6.Prefix, delimiter + delimiter + delimiter); CloudPageBlob blob2 = directory6.GetPageBlobReference("Blob1"); - Assert.AreEqual(blob2.Name, delimiter+delimiter+delimiter+"Blob1"); + Assert.AreEqual(blob2.Name, delimiter + delimiter + delimiter + "Blob1"); Assert.AreEqual(blob2.Uri, blob.Uri); } finally @@ -677,8 +705,5 @@ public void CloudBlobDirectoryDelimitersInARow() } } } - - } -} - +} \ No newline at end of file diff --git a/Test/ClassLibraryCommon/Core/LoggingTests.cs b/Test/ClassLibraryCommon/Core/LoggingTests.cs index 58b0de125..f2cfb99a2 100644 --- a/Test/ClassLibraryCommon/Core/LoggingTests.cs +++ b/Test/ClassLibraryCommon/Core/LoggingTests.cs @@ -113,7 +113,7 @@ public void LoggingTestAPM() HttpStatusCode.NotFound); Assert.AreEqual(1, TestLogListener.RequestCount); Assert.AreEqual(1, TestLogListener.ErrorCount); - Assert.AreEqual(2, TestLogListener.WarningCount); + Assert.AreEqual(1, TestLogListener.WarningCount); Assert.AreEqual(operationContext.ClientRequestID, TestLogListener.LastRequestID); operationContext.ClientRequestID = Guid.NewGuid().ToString(); @@ -124,7 +124,7 @@ public void LoggingTestAPM() container.EndCreateIfNotExists(result); Assert.AreEqual(2, TestLogListener.RequestCount); Assert.AreEqual(1, TestLogListener.ErrorCount); - Assert.AreEqual(3, TestLogListener.WarningCount); + Assert.AreEqual(2, TestLogListener.WarningCount); Assert.AreEqual(operationContext.ClientRequestID, TestLogListener.LastRequestID); string lastLoggedRequestID = TestLogListener.LastRequestID; @@ -137,7 +137,7 @@ public void LoggingTestAPM() container.EndDeleteIfExists(result); Assert.AreEqual(2, TestLogListener.RequestCount); Assert.AreEqual(1, TestLogListener.ErrorCount); - Assert.AreEqual(3, TestLogListener.WarningCount); + Assert.AreEqual(2, TestLogListener.WarningCount); Assert.AreEqual(lastLoggedRequestID, TestLogListener.LastRequestID); } } @@ -170,7 +170,7 @@ public void LoggingTestTask() HttpStatusCode.NotFound); Assert.AreEqual(1, TestLogListener.RequestCount); Assert.AreEqual(1, TestLogListener.ErrorCount); - Assert.AreEqual(2, TestLogListener.WarningCount); + Assert.AreEqual(1, TestLogListener.WarningCount); Assert.AreEqual(operationContext.ClientRequestID, TestLogListener.LastRequestID); operationContext.ClientRequestID = Guid.NewGuid().ToString(); @@ -178,7 +178,7 @@ public void LoggingTestTask() container.CreateIfNotExistsAsync(null, operationContext).Wait(); Assert.AreEqual(2, TestLogListener.RequestCount); Assert.AreEqual(1, TestLogListener.ErrorCount); - Assert.AreEqual(3, TestLogListener.WarningCount); + Assert.AreEqual(2, TestLogListener.WarningCount); Assert.AreEqual(operationContext.ClientRequestID, TestLogListener.LastRequestID); string lastLoggedRequestID = TestLogListener.LastRequestID; @@ -188,7 +188,7 @@ public void LoggingTestTask() container.DeleteIfExistsAsync(null, null, operationContext).Wait(); Assert.AreEqual(2, TestLogListener.RequestCount); Assert.AreEqual(1, TestLogListener.ErrorCount); - Assert.AreEqual(3, TestLogListener.WarningCount); + Assert.AreEqual(2, TestLogListener.WarningCount); Assert.AreEqual(lastLoggedRequestID, TestLogListener.LastRequestID); } finally diff --git a/Test/Common/Core/StorageUriTests.cs b/Test/Common/Core/StorageUriTests.cs index b868c6a97..ccfd3042d 100644 --- a/Test/Common/Core/StorageUriTests.cs +++ b/Test/Common/Core/StorageUriTests.cs @@ -175,7 +175,7 @@ public void BlobTypesWithStorageUri() CloudBlobDirectory directory = container.GetDirectoryReference("directory"); Assert.IsTrue(directoryUri.Equals(directory.StorageUri)); Assert.IsTrue(directoryUri.PrimaryUri.Equals(directory.Uri)); - Assert.IsNull(directory.Parent); + Assert.IsNotNull(directory.Parent); Assert.IsTrue(containerUri.Equals(directory.Container.StorageUri)); Assert.IsTrue(endpoint.Equals(directory.ServiceClient.StorageUri)); diff --git a/Test/WindowsDesktop/Properties/AssemblyInfo.cs b/Test/WindowsDesktop/Properties/AssemblyInfo.cs index 5b6996442..be05771ef 100644 --- a/Test/WindowsDesktop/Properties/AssemblyInfo.cs +++ b/Test/WindowsDesktop/Properties/AssemblyInfo.cs @@ -31,5 +31,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("3.0.2.0")] -[assembly: AssemblyFileVersion("3.0.2.0")] +[assembly: AssemblyVersion("3.0.3.0")] +[assembly: AssemblyFileVersion("3.0.3.0")] diff --git a/Test/WindowsPhone/Blob/BlobCancellationUnitTests.cs b/Test/WindowsPhone/Blob/BlobCancellationUnitTests.cs index e5fb72530..361bc0328 100644 --- a/Test/WindowsPhone/Blob/BlobCancellationUnitTests.cs +++ b/Test/WindowsPhone/Blob/BlobCancellationUnitTests.cs @@ -48,7 +48,7 @@ public void MyTestCleanup() } } - /*[TestMethod] + [TestMethod] [Description("Cancel blob download to stream")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] @@ -70,16 +70,15 @@ public async Task CloudBlockBlobDownloadToStreamCancelAsync() using (MemoryStream downloadedBlob = new MemoryStream()) { OperationContext operationContext = new OperationContext(); - Task task = blob.DownloadToStreamAsync(downloadedBlob, null, null, operationContext); - await Task.Delay(100); - task.Cancel(); + CancellationTokenSource source = new CancellationTokenSource(100); + Task task = blob.DownloadToStreamAsync(downloadedBlob, null, null, operationContext, source.Token); try { - await action; + await task; } catch (Exception) { - Assert.AreEqual(operationContext.LastResult.Exception.Message, "A task was canceled."); + Assert.AreEqual(operationContext.LastResult.Exception.Message, "Operation was canceled by user."); Assert.AreEqual(operationContext.LastResult.HttpStatusCode, 306); //Assert.AreEqual(operationContext.LastResult.HttpStatusMessage, "Unused"); } @@ -113,16 +112,15 @@ public async Task CloudBlockBlobUploadFromStreamCancelAsync() using (ManualResetEvent waitHandle = new ManualResetEvent(false)) { OperationContext operationContext = new OperationContext(); - IAsyncAction action = blob.UploadFromStreamAsync(originalBlob, null, null, operationContext); - await Task.Delay(100); - action.Cancel(); + CancellationTokenSource source = new CancellationTokenSource(100); + Task task = blob.UploadFromStreamAsync(originalBlob, null, null, operationContext, source.Token); try { - await action; + await task; } catch (Exception) { - Assert.AreEqual(operationContext.LastResult.Exception.Message, "A task was canceled."); + Assert.AreEqual(operationContext.LastResult.Exception.Message, "Operation was canceled by user."); Assert.AreEqual(operationContext.LastResult.HttpStatusCode, 306); //Assert.AreEqual(operationContext.LastResult.HttpStatusMessage, "Unused"); } @@ -134,6 +132,6 @@ public async Task CloudBlockBlobUploadFromStreamCancelAsync() { container.DeleteIfExistsAsync().Wait(); } - }*/ + } } } diff --git a/Test/WindowsPhone/Blob/CloudBlobDirectoryTest.cs b/Test/WindowsPhone/Blob/CloudBlobDirectoryTest.cs index c5e4eb94b..aaf02770a 100644 --- a/Test/WindowsPhone/Blob/CloudBlobDirectoryTest.cs +++ b/Test/WindowsPhone/Blob/CloudBlobDirectoryTest.cs @@ -260,7 +260,10 @@ public void CloudBlobDirectoryGetParentOnRootAsync() { CloudBlobDirectory root = container.GetDirectoryReference("TopDir1" + delimiter); CloudBlobDirectory parent = root.Parent; - Assert.IsNull(parent); + Assert.IsNotNull(parent); + + CloudBlobDirectory empty = parent.Parent; + Assert.IsNull(empty); } finally { diff --git a/Test/WindowsPhone/Properties/AssemblyInfo.cs b/Test/WindowsPhone/Properties/AssemblyInfo.cs index a8b534885..9ac93a061 100644 --- a/Test/WindowsPhone/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhone/Properties/AssemblyInfo.cs @@ -32,6 +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("3.0.2.0")] -[assembly: AssemblyFileVersion("3.0.2.0")] +[assembly: AssemblyVersion("3.0.3.0")] +[assembly: AssemblyFileVersion("3.0.3.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Test/WindowsRuntime/Blob/CloudBlobDirectoryTest.cs b/Test/WindowsRuntime/Blob/CloudBlobDirectoryTest.cs index 352c8b8e1..8aad96dbd 100644 --- a/Test/WindowsRuntime/Blob/CloudBlobDirectoryTest.cs +++ b/Test/WindowsRuntime/Blob/CloudBlobDirectoryTest.cs @@ -19,6 +19,8 @@ namespace Microsoft.WindowsAzure.Storage.Blob { using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; using Microsoft.WindowsAzure.Storage; + using Microsoft.WindowsAzure.Storage.Core; + using Microsoft.WindowsAzure.Storage.Core.Util; using System; using System.Collections.Generic; using System.Linq; @@ -28,7 +30,7 @@ namespace Microsoft.WindowsAzure.Storage.Blob [TestClass] public class CloudBlobDirectoryTest : BlobTestBase { - string[] Delimiters = new string[] { "$", "@", "-", "%", "/", "|"}; + string[] Delimiters = new string[] { ":", "$", "@", "-", "%", "/", "|", "$$", "::", "//" }; private async Task CloudBlobDirectorySetupWithDelimiterAsync(CloudBlobContainer container, string delimiter = "/") { @@ -102,9 +104,40 @@ public async Task CloudBlobDirectoryGetParentAsync() await blob.CreateAsync(0); Assert.IsTrue(await blob.ExistsAsync()); Assert.AreEqual("Dir1" + delimiter + "Blob1", blob.Name); + + // get the blob's parent CloudBlobDirectory parent = blob.Parent; Assert.AreEqual(parent.Prefix, "Dir1" + delimiter); - await blob.DeleteAsync(); + + // get container as parent + CloudBlobDirectory root = parent.Parent; + Assert.AreEqual(root.Prefix, ""); + + // make sure the parent of the container dir is null + CloudBlobDirectory empty = root.Parent; + Assert.IsNull(empty); + + // from container, get directory reference to container + root = container.GetDirectoryReference(""); + Assert.AreEqual("", root.Prefix); + Assert.AreEqual(container.Uri.AbsoluteUri, root.Uri.AbsoluteUri); + + BlobResultSegment segment = await root.ListBlobsSegmentedAsync(null); + List list = new List(); + list.AddRange(segment.Results); + while (segment.ContinuationToken != null) + { + segment = await container.ListBlobsSegmentedAsync(segment.ContinuationToken); + list.AddRange(segment.Results); + } + + Assert.AreEqual(1, list.Count); + + // make sure the parent of the container dir is null + empty = root.Parent; + Assert.IsNull(empty); + + await blob.DeleteIfExistsAsync(); } finally { @@ -260,7 +293,10 @@ public void CloudBlobDirectoryGetParentOnRootAsync() { CloudBlobDirectory root = container.GetDirectoryReference("TopDir1" + delimiter); CloudBlobDirectory parent = root.Parent; - Assert.IsNull(parent); + Assert.IsNotNull(parent); + + CloudBlobDirectory empty = parent.Parent; + Assert.IsNull(empty); } finally { @@ -485,7 +521,5 @@ public void CloudBlobDirectoryDelimitersInARowAsync() } } } - - } } \ No newline at end of file diff --git a/Test/WindowsRuntime/Properties/AssemblyInfo.cs b/Test/WindowsRuntime/Properties/AssemblyInfo.cs index f3be4b1dc..0dbbaafd0 100644 --- a/Test/WindowsRuntime/Properties/AssemblyInfo.cs +++ b/Test/WindowsRuntime/Properties/AssemblyInfo.cs @@ -22,5 +22,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("3.0.2.0")] -[assembly: AssemblyFileVersion("3.0.2.0")] +[assembly: AssemblyVersion("3.0.3.0")] +[assembly: AssemblyFileVersion("3.0.3.0")] diff --git a/changelog.txt b/changelog.txt index cb7e1276a..36c9a7a37 100644 --- a/changelog.txt +++ b/changelog.txt @@ -187,3 +187,13 @@ Issues fixed in 3.0.2.0 : - Queues: Fixed an issue causing NullReferenceException when trying to re-create an existing queue. - Tables: Fixed an issue with TableServiceContext causing NullReferenceException when the error response cannot be parsed. - Tables (RT): Do not allow users to set JsonFullMetadata format on RequestOptions in RT, because JSON is not supported on the RT library yet. + +Issues fixed in 3.0.3.0 : + + - All: Fixed an issue that was causing a deadlock because the cancellation registration was being de-registered while holding on to a lock that the cancellation callback was waiting on. + - All: Fixed an issue where parsing exception information out of the response stream while using async was blocking on IO calls. + - All (WP): Fixed an issue with cancellation and timeout during upload and download causing a thread to hang. HttpWebRequest.Abort is not called during stream operations anymore. + - Blobs: Streams opened with OpenRead methods use the user-provided buffer pooling implementation. + - Blobs: If the parent of a blob is the container, Cloud{BlockBlob|PageBlob|BlobDirectory}.Parent returns a valid CloudBlobDirectory with an empty prefix. Similarly, container.GetDirectoryReference("") gets a valid CloudBlobDirectory representing the container. + - Tables (Perf): Fixed an issue where the entity properties were being enumerated twice during table write operations while using JSON. + - Tables (Perf): Parse the URI for the account name only once in the client constructor and store it so that all table operations can use the stored value.