/// <summary> /// Splits a stream into chunks using a <see cref="ChunkStream"/>, and wraps them into /// <see cref="StreamedDataBlock"/> instances that can be sent to an <see cref="IUploadTransferHandler"/>. /// This overload allows to resume a transfer and start with a given offset. /// <br/>This extension method also takes care of implicitly completing the upload by setting the /// <see cref="IDataBlock.IsLastBlock"/> property of the last block to true. /// </summary> /// <param name="sourceStream">The source stream that provides the data to be uploaded.</param> /// <param name="token">An upload token that defines the resource.</param> /// <param name="resourceLength">The total length of the submitted stream.</param> /// <param name="blockSize">The block size to be used. All blocks (except the last one) will have /// this size.</param> /// <param name="initialBlockNumber">The initial block number to be used.</param> /// <param name="offset">The offset of the first written block (0 to start at the beginning of the stream).</param> /// <param name="writerAction">An action that is being invoked for every created <see cref="StreamedDataBlock"/> /// instance.</param> /// <remarks>The position within the stream is only set according to the submitted /// <paramref name="offset"/> if the underlying <paramref name="sourceStream"/> supports seeking as indicated by /// its <see cref="Stream.CanSeek"/> property.</remarks> public static void WriteTo(this Stream sourceStream, UploadToken token, long resourceLength, int blockSize, long initialBlockNumber, long offset, Action <StreamedDataBlock> writerAction) { long remaining = resourceLength; long position = offset; long blockNumber = initialBlockNumber; while (remaining > 0) { //decorate the stream with a chunk stream that limits access to a block of data int chunkSize = (int)Math.Min(remaining, blockSize); ChunkStream cs = new ChunkStream(sourceStream, chunkSize, position, sourceStream.CanSeek); StreamedDataBlock dataBlock = new StreamedDataBlock { TransferTokenId = token.TransferId, BlockLength = chunkSize, BlockNumber = blockNumber, Data = cs, Offset = position }; //update position within stream and remaining bytes position += chunkSize; remaining -= chunkSize; blockNumber++; if (remaining == 0) { //implicitly complete the transfer by marking the last block dataBlock.IsLastBlock = true; } writerAction(dataBlock); } }
/// <summary> /// Reads a block via a streaming channel, which enables a more resource friendly /// data transmission (compared to sending the whole data of the block at once). /// </summary> /// <param name="transferId"></param> /// <param name="blockNumber"></param> /// <returns></returns> public StreamedDataBlock ReadBlockStreamed(string transferId, long blockNumber) { //this func creates the returned DataBlock by reading a chunk of data //from the underlying stream. Func <DownloadTransfer, long, StreamedDataBlock> func = (dt, position) => { DownloadToken token = dt.Token; //check if we can use the max block size long streamLength = dt.Stream.Length; long blockLength = Math.Min(token.DownloadBlockSize.Value, streamLength - position); if (blockLength < 0) { blockLength = 0; } ChunkStream stream = new ChunkStream(dt.Stream, blockLength, position, false); //if we're reading to the end of the stream, we're done bool isLastBlock = position + blockLength == streamLength; return(new StreamedDataBlock { TransferTokenId = transferId, BlockNumber = blockNumber, BlockLength = blockLength, Offset = position, Data = stream, IsLastBlock = isLastBlock }); }; return(PrepareBlockReading(transferId, blockNumber, func)); }