// 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;
}
}
}
}