// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Buffers; using System.IO; using System.Threading; using System.Threading.Tasks; namespace EpicGames.Core { /// /// Utility methods for streams /// public static class StreamUtils { /// /// Issues read calls until the given buffer has been filled with data or the stream is at an end /// /// Stream to read from /// Buffer to receive the output data /// Cancellation token for the operation public static async Task ReadGreedyAsync(this Stream stream, Memory buffer, CancellationToken cancellationToken = default) { int length = 0; while (length < buffer.Length) { int readLength = await stream.ReadAsync(buffer.Slice(length), cancellationToken); if (readLength == 0) { break; } length += readLength; } return length; } /// /// Reads a fixed amount of data from a stream, throwing an exception if the entire buffer cannot be read. /// /// Stream to read from /// Buffer to receive the output data public static void ReadFixedLengthBytes(this Stream stream, Span buffer) { for (int offset = 0; offset < buffer.Length;) { int readLength = stream.Read(buffer.Slice(offset)); if (readLength == 0) { throw new EndOfStreamException($"Unexpected end of stream while trying to read {buffer.Length} bytes."); } offset += readLength; } } /// /// Reads a fixed amount of data from a stream, throwing an exception if the entire buffer cannot be read. /// /// Stream to read from /// Buffer to receive the output data /// Cancellation token for the operation public static async Task ReadFixedLengthBytesAsync(this Stream stream, Memory buffer, CancellationToken cancellationToken = default) { for (int offset = 0; offset < buffer.Length;) { int readLength = await stream.ReadAsync(buffer.Slice(offset), cancellationToken); if (readLength == 0) { throw new EndOfStreamException($"Unexpected end of stream while trying to read {buffer.Length} bytes."); } offset += readLength; } } /// /// Read the contents of a file async using double buffering /// /// Stream to read from /// Cancellation token used to terminate processing /// Contents of the stream public static async Task ReadAllBytesAsync(this Stream stream, CancellationToken cancellationToken = default) { using MemoryStream memoryStream = new MemoryStream(); await stream.CopyToAsync(memoryStream, cancellationToken); return memoryStream.ToArray(); } /// /// Read the contents of a file async using double buffering /// /// Stream to read from /// If available, the file size so an appropriate buffer size can be used /// Minimum size of the buffer /// Maximum size of the buffer /// Callback used to send read data back to the caller /// Cancellation token used to terminate processing /// Contents of the stream public static async Task ReadAllBytesAsync(this Stream stream, long fileSizeHint, int minBufferSize, int maxBufferSize, Func, Task> callback, CancellationToken cancellationToken = default) { int bufferLength = minBufferSize; if (fileSizeHint > minBufferSize) { bufferLength = (int)(fileSizeHint <= maxBufferSize ? fileSizeHint : maxBufferSize); } await ReadAllBytesAsync(stream, bufferLength, callback, cancellationToken); } /// /// Read the contents of a file async using double buffering /// /// Data to compute the hash for /// Size of the internal buffer /// Callback used to send read data back to the caller /// Cancellation token used to terminate processing /// New content hash instance containing the hash of the data public static async Task ReadAllBytesAsync(this Stream stream, int bufferLength, Func, Task> callback, CancellationToken cancellationToken = default) { using IMemoryOwner owner = MemoryPool.Shared.Rent(bufferLength * 2); Memory buffer = owner.Memory; int readBufferOffset = 0; Memory appendBuffer = Memory.Empty; for (; ; ) { // Start a read into memory Memory readBuffer = buffer[readBufferOffset..(readBufferOffset + bufferLength)]; Task readTask = Task.Run(async () => await stream.ReadAsync(readBuffer, cancellationToken), cancellationToken); // In the meantime, append the last data that was read to the tree if (appendBuffer.Length > 0) { await callback(appendBuffer[0..appendBuffer.Length]); } // Wait for the read to finish int numBytes = await readTask; if (numBytes == 0) { break; } // Switch the buffers around appendBuffer = readBuffer[0..numBytes]; readBufferOffset ^= bufferLength; } } } }