/// <summary>Constructs a BlobLogBase object.</summary> /// <param name="container">Indicates the container where log blobs should be placed.</param> /// <param name="extension">Indicates the extension (initial period is optional) of the log blob.</param> /// <param name="mimeType">Indicates the mime type of the log blob.</param> /// <param name="header">Indicates the header (first block) of the log blob. Pass 'null' if you do not want a header.</param> /// <param name="maxEntries">Indicates the maximum number of blocks the blob should have. Old blocks are deleted when going over this limit.</param> protected internal BlobLogBase(CloudBlobContainer container, String extension, String mimeType, Byte[] header = null, Int32 maxEntries = 49000) { m_container = container; m_extension = extension.StartsWith(".") ? extension : "." + extension; m_info = new AppendBlobBlockInfo { MimeType = mimeType, Header = header, MaxEntries = maxEntries }; }
/// <summary>Appends a block to the end of a block blob.</summary> /// <param name="blob">The blob to append a block to.</param> /// <param name="info">Additional info used when the blob is first being created.</param> /// <param name="dataToAppend">The data to append as a block to the end of the blob.</param> /// <param name="options">The blob request options.</param> /// <param name="operationContext">The operation context.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A Task indicating when the operation has completed.</returns> public static async Task AppendBlockAsync(this CloudBlockBlob blob, AppendBlobBlockInfo info, Byte[] dataToAppend, BlobRequestOptions options = null, OperationContext operationContext = null, CancellationToken cancellationToken = default(CancellationToken)) { blob.Properties.ContentType = info.MimeType; //blob.Properties.ContentDisposition = "attachment; filename=\"fname.ext\""; // NOTE: Put Block ignores access condition and so it always succeeds List <String> blockList = new List <String>(); while (true) { blockList.Clear(); AccessCondition accessCondition; try { blockList.AddRange((await blob.DownloadBlockListAsync(BlockListingFilter.Committed, null, options, operationContext, cancellationToken).ConfigureAwait(false)).Select(lbi => lbi.Name)); // 404 if blob not found accessCondition = AccessCondition.GenerateIfMatchCondition(blob.Properties.ETag); // Write if blob matches what we read } catch (StorageException se) { if (!se.Matches(HttpStatusCode.NotFound, BlobErrorCodeStrings.BlobNotFound)) { throw; } accessCondition = AccessCondition.GenerateIfNoneMatchCondition("*"); // Write if blob doesn't exist } try { if (blockList.Count == 0) // Blob doesn't exist yet, add header (if specified) // Notify client code that new blob is about to be created { info.OnCreatingBlob(blob); if (info.Header != null) // Write header to new blob { String headerBlockId = Guid.NewGuid().ToString().Encode().ToBase64String(); await blob.PutBlockAsync(headerBlockId, new MemoryStream(info.Header), null).ConfigureAwait(false); blockList.Add(headerBlockId); } } // Upload new block & add it's Id to the block list String blockId = Guid.NewGuid().ToString().Encode().ToBase64String(); await blob.PutBlockAsync(blockId, new MemoryStream(dataToAppend), null).ConfigureAwait(false); blockList.Add(blockId); // If too many blocks, remove old block (but not the header if it exists) var maxEntries = info.MaxEntries + ((info.Header == null) ? 0 : 1); if (blockList.Count > maxEntries) { blockList.RemoveAt((info.Header == null) ? 0 : 1); } // Upload the new block list await blob.PutBlockListAsync(blockList, AccessCondition.GenerateIfMatchCondition(blob.Properties.ETag), options, operationContext, cancellationToken).ConfigureAwait(false); // 409 if blob created behind our back; 400 if block Id doesn't exist (happens in another PC calls PutBlockList after our PutBlock) break; // If successful, we're done; don't retry } catch (StorageException se) { // Blob got created behind our back, retry if (se.Matches(HttpStatusCode.Conflict, BlobErrorCodeStrings.BlobAlreadyExists)) { continue; } // Blob got created or modified behind our back, retry if (se.Matches(HttpStatusCode.PreconditionFailed)) { continue; } // Another PC called PutBlockList between our PutBlock & PutBlockList, // our block(s) got destroyed, retry if (se.Matches(HttpStatusCode.BadRequest, BlobErrorCodeStrings.InvalidBlockList)) { continue; } throw; } } }