public virtual async Task <StorageResponseHolder> Update(string key, byte[] body,
                                                                 string contentType       = null, string contentEncoding = null,
                                                                 string version           = null, string oldVersion      = null,
                                                                 string directory         = null, IEnumerable <KeyValuePair <string, string> > metadata = null,
                                                                 CancellationToken?cancel = null, bool createSnapshot = false, bool useEncryption = true)
        {
            var request = new StorageRequestHolder(key, cancel, directory);

            return(await CallCloudBlockBlob(request,
                                            async (rq, rs, exists) =>
            {
                if (!exists)
                {
                    rq.CopyTo(rs);
                    rs.StatusCode = 404;
                    rs.IsSuccess = false;
                    return;
                }

                if (oldVersion != null && oldVersion != rq.VersionId)
                {
                    MetadataGet(rq.Blob, rs);
                    rs.StatusCode = 409;
                    rs.IsSuccess = false;
                    return;
                }

                if (createSnapshot)
                {
                    await rq.Blob.CreateSnapshotAsync(rq.Fields,
                                                      AccessCondition.GenerateIfMatchCondition(rq.ETag), mOptions, mContext, rq.CancelSet);
                    MetadataGet(rq.Blob, rq);
                }

                rq.ContentType = contentType ?? rs.ContentType;
                rq.ContentEncoding = contentEncoding ?? rs.ContentEncoding;
                rq.VersionId = version;
                MetadataSet(rq.Blob, rq);

                // If encryption provided encrypt the body
                var uploadBody = body;
                if (useEncryption && mEncryption != null)
                {
                    uploadBody = mEncryption.Encrypt(body);
                }

                await rq.Blob.UploadFromByteArrayAsync(uploadBody, 0, uploadBody.Length,
                                                       AccessCondition.GenerateIfMatchCondition(rq.ETag), mOptions, mContext, rq.CancelSet);

                MetadataGet(rq.Blob, rs);
                rs.Data = body;
                rs.IsSuccess = true;
                rs.StatusCode = 200;
            }));
        }
        /// <summary>
        /// This is wrapper class that provides generic exception handling support
        /// and retrieves the standard metadata for each request.
        /// </summary>
        /// <param name="rq">The request.</param>
        /// <param name="rs">The response.</param>
        /// <param name="action">The async action task.</param>
        /// <returns>Returns a task with the response.</returns>
        protected async Task <StorageResponseHolder> CallCloudBlockBlob(StorageRequestHolder rq
                                                                        , Func <StorageRequestHolder, StorageResponseHolder, bool, Task> action)
        {
            int start = StatisticsInternal.ActiveIncrement();
            var rs    = new StorageResponseHolder();

            try
            {
                var refEntityDirectory = mEntityContainer.GetDirectoryReference(rq.Directory);
                rq.Blob = refEntityDirectory.GetBlockBlobReference(rq.SafeKey);

                bool exists = await rq.Blob.ExistsAsync(rq.CancelSet);

                if (exists)
                {
                    MetadataGet(rq.Blob, rq);
                    exists ^= rq.IsDeleted;
                }

                await action(rq, rs, exists);
            }
            catch (StorageException sex)
            {
                rs.Ex         = sex;
                rs.IsSuccess  = false;
                rs.StatusCode = sex.RequestInformation.HttpStatusCode;
                rs.IsTimeout  = rs.StatusCode == 500 || rs.StatusCode == 503;
            }
            catch (TaskCanceledException tcex)
            {
                rs.Ex         = tcex;
                rs.IsTimeout  = true;
                rs.IsSuccess  = false;
                rs.StatusCode = 502;
            }
            catch (Exception ex)
            {
                rs.Ex         = ex;
                rs.IsSuccess  = false;
                rs.StatusCode = 500;
            }
            finally
            {
                if (!rs.IsSuccess)
                {
                    StatisticsInternal.ErrorIncrement();
                }
                StatisticsInternal.ActiveDecrement(start);
            }

            return(rs);
        }
        public virtual async Task <StorageResponseHolder> Create(string key
                                                                 , byte[] body
                                                                 , string contentType     = null
                                                                 , string contentEncoding = null
                                                                 , string version         = null
                                                                 , string directory       = null
                                                                 , IEnumerable <KeyValuePair <string, string> > metadata = null
                                                                 , CancellationToken?cancel = null
                                                                 , bool useEncryption       = true)
        {
            var request = new StorageRequestHolder(key, cancel, directory);

            return(await CallCloudBlockBlob(request,
                                            async (rq, rs, exists) =>
            {
                if (!exists)
                {
                    //Check for a soft delete condition.
                    var access = rq.ETag != null ? AccessCondition.GenerateIfMatchCondition(rq.ETag) : AccessCondition.GenerateIfNotExistsCondition();

                    rq.ContentType = contentType;
                    rq.ContentEncoding = contentEncoding;
                    rq.VersionId = version;
                    if (metadata != null)
                    {
                        rq.Fields = metadata.ToDictionary((k) => k.Key, (k) => k.Value);
                    }
                    MetadataSet(rq.Blob, rq);

                    // If encryption provided encrypt the body
                    var uploadBody = body;
                    if (useEncryption && mEncryption != null)
                    {
                        uploadBody = mEncryption.Encrypt(body);
                    }

                    await rq.Blob.UploadFromByteArrayAsync(uploadBody, 0, uploadBody.Length,
                                                           access, mOptions, mContext, rq.CancelSet);

                    MetadataGet(rq.Blob, rs);
                    rs.Data = body;
                }
                else
                {
                    rq.CopyTo(rs);
                }

                rs.IsSuccess = !exists;
                rs.StatusCode = exists ? 409 : 201;
            }));
        }
        public virtual async Task <StorageResponseHolder> Delete(string key, string directory      = null, string version = null
                                                                 , CancellationToken?cancel        = null
                                                                 , bool createSnapshotBeforeDelete = true, bool hardDelete = false)
        {
            var request = new StorageRequestHolder(key, cancel, directory);

            return(await CallCloudBlockBlob(request,
                                            async (rq, rs, exists) =>
            {
                //Does the entity currently exist?
                if (!exists)
                {
                    rq.CopyTo(rs);
                    rs.IsSuccess = false;
                    rs.StatusCode = 404;
                    return;
                }
                //OK, do the version match?
                if (version != null && rq.VersionId != version)
                {
                    MetadataGet(rq.Blob, rs);
                    rs.IsSuccess = false;
                    rs.StatusCode = 409;
                    return;
                }

                if (hardDelete)
                {
                    //OK, we hard delete the entity and remove any snapshots that may be there.
                    await rq.Blob.DeleteAsync(DeleteSnapshotsOption.IncludeSnapshots, AccessCondition.GenerateIfMatchCondition(rq.ETag), mOptions, mContext, rq.CancelSet);
                }
                else
                {
                    if (createSnapshotBeforeDelete)
                    {
                        await rq.Blob.CreateSnapshotAsync(rq.Fields,
                                                          AccessCondition.GenerateIfMatchCondition(rq.ETag), mOptions, mContext, rq.CancelSet);
                    }
                    //Ok, we are going to soft delete the entity.
                    MetadataSet(rq.Blob, rq, deleted: true);
                    await rq.Blob.SetMetadataAsync(AccessCondition.GenerateIfMatchCondition(rq.ETag), mOptions, mContext, rq.CancelSet);
                }

                MetadataGet(rq.Blob, rs);
                rs.IsSuccess = true;
                rs.StatusCode = 200;
            }));
        }
        public virtual async Task <StorageResponseHolder> Version(string key, string directory = null, CancellationToken?cancel = null)
        {
            var request = new StorageRequestHolder(key, cancel, directory);

            return(await CallCloudBlockBlob(request,
                                            async (rq, rs, exists) =>
            {
                if (exists)
                {
                    MetadataGet(rq.Blob, rs);
                }
                else
                {
                    rq.CopyTo(rs);
                }
                rs.IsSuccess = exists;
                rs.StatusCode = exists ? 200 : 404;
            }));
        }
        public virtual async Task <StorageResponseHolder> Read(string key
                                                               , string directory         = null
                                                               , CancellationToken?cancel = null
                                                               , bool useEncryption       = true)
        {
            var request = new StorageRequestHolder(key, cancel, directory);

            return(await CallCloudBlockBlob(request,
                                            async (rq, rs, exists) =>
            {
                if (exists)
                {
                    var sData = new MemoryStream();

                    await rq.Blob.DownloadToStreamAsync(sData, rq.CancelSet);

                    rs.Data = new byte[sData.Length];
                    sData.Position = 0;
                    sData.Read(rs.Data, 0, rs.Data.Length);

                    // If encryption provided decrypt the blob
                    if (useEncryption && mEncryption != null)
                    {
                        rs.Data = mEncryption.Decrypt(rs.Data);
                    }

                    MetadataGet(rq.Blob, rs);
                }
                else
                {
                    rq.CopyTo(rs);
                }

                rs.IsSuccess = exists;
                rs.StatusCode = exists ? 200 : 404;
            }));
        }