Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 66 additions & 1 deletion CSUtilities.Tests/IO/StreamIOTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,42 @@ namespace CSUtilities.Tests.IO;

public class StreamIOTests : IDisposable
{
private sealed class PartialReadMemoryStream : MemoryStream
{
public PartialReadMemoryStream(byte[] data) : base(data) { }

public override int Read(byte[] buffer, int offset, int count)
{
return base.Read(buffer, offset, Math.Min(count, 2));
}

public override Task<int> ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken)
{
return base.ReadAsync(buffer, offset, Math.Min(count, 2), cancellationToken);
}
}

private sealed class NonSeekableStream : Stream
{
private readonly Stream _inner;

public NonSeekableStream(byte[] data)
{
this._inner = new MemoryStream(data);
}

public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => throw new NotSupportedException();
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
public override void Flush() { }
public override int Read(byte[] buffer, int offset, int count) => this._inner.Read(buffer, offset, Math.Min(count, 2));
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
}

private readonly MemoryStream _memoryStream;
private readonly StreamIO _streamIO;

Expand Down Expand Up @@ -41,6 +77,35 @@ public void WriteAndReadBytes_WorksCorrectly()
Assert.Equal(data, _streamIO.ReadBytes(data.Length));
}

[Fact]
public void ConstructorBuffersANonSeekablePartialReadStream()
{
byte[] data = { 1, 2, 3, 4, 5, 6 };
using NonSeekableStream source = new NonSeekableStream(data);
using StreamIO stream = new StreamIO(source);

Assert.True(stream.Stream.CanSeek);
Assert.Equal(data, stream.ReadBytes(data.Length));
}

[Fact]
public void ReadBytesCompletesPartialReads()
{
byte[] data = { 1, 2, 3, 4, 5, 6 };
using StreamIO stream = new StreamIO(new PartialReadMemoryStream(data));

Assert.Equal(data, stream.ReadBytes(data.Length));
}

[Fact]
public async Task ReadBytesAsyncCompletesPartialReads()
{
byte[] data = { 1, 2, 3, 4, 5, 6 };
using StreamIO stream = new StreamIO(new PartialReadMemoryStream(data));

Assert.Equal(data, await stream.ReadBytesAsync(data.Length));
}

[Fact]
public void WriteAndReadString_WorksCorrectly()
{
Expand Down Expand Up @@ -206,4 +271,4 @@ public void ReadBytes_ThrowsOnEndOfStream()
{
Assert.Throws<EndOfStreamException>(() => _streamIO.ReadBytes(1));
}
}
}
108 changes: 76 additions & 32 deletions CSUtilities/IO/StreamIO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,36 @@ public StreamIO(string filename, FileMode mode, FileAccess access)
/// <param name="resetPosition"></param>
public StreamIO(Stream stream, bool createCopy, bool resetPosition)
{
long position = stream.Position;
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}

//Check if supports seeking
//Readers require seeking. A non-seekable input therefore has to be buffered from its
//current position; querying Position or Length first would itself throw.
if (!stream.CanSeek || createCopy)
{
stream.Position = 0;
//Create a copy of the stream to allow seeking
byte[] buffer = new byte[stream.Length];
stream.Read(buffer, 0, buffer.Length);
_stream = (Stream)new MemoryStream(buffer);
stream.Position = position;
MemoryStream copy = new MemoryStream();
if (stream.CanSeek)
{
long position = stream.Position;
try
{
stream.Position = 0;
stream.CopyTo(copy);
}
finally
{
stream.Position = position;
}
}
else
{
stream.CopyTo(copy);
}

copy.Position = 0;
_stream = copy;
}
else
{
Expand Down Expand Up @@ -116,14 +135,15 @@ public byte[] GetBytes(int offset, int length)
//Set the position to the begining
this.Position = offset;

byte[] buffer = this.ReadBytes(length);
//if (this.m_stream.Read(buffer, offset, length) < length)
// throw new EndOfStreamException();

//Reset the position
this.Position = save;

return buffer;
try
{
return this.ReadBytes(length);
}
finally
{
//A failed read must not leave this utility at the requested offset.
this.Position = save;
}
}

public async Task<byte[]> GetBytesAsync(int offset, int length)
Expand All @@ -136,14 +156,14 @@ public async Task<byte[]> GetBytesAsync(int offset, int length)
//Set the position to the begining
this.Position = offset;

byte[] buffer = await this.ReadBytesAsync(length);
//if (this.m_stream.Read(buffer, offset, length) < length)
// throw new EndOfStreamException();

//Reset the position
this.Position = save;

return buffer;
try
{
return await this.ReadBytesAsync(length);
}
finally
{
this.Position = save;
}
}

/// <summary>
Expand All @@ -163,9 +183,15 @@ public byte LookByte()
/// <returns></returns>
public byte[] LookBytes(int count)
{
byte[] bs = this.ReadBytes(count);
this.Position -= count;
return bs;
long position = this.Position;
try
{
return this.ReadBytes(count);
}
finally
{
this.Position = position;
}
}

/// <summary>
Expand Down Expand Up @@ -214,8 +240,17 @@ public virtual byte[] ReadBytes(int length)

byte[] buffer = new byte[length];

if (this._stream.Read(buffer, 0, length) < length)
throw new EndOfStreamException();
int offset = 0;
while (offset < length)
{
int read = this._stream.Read(buffer, offset, length - offset);
if (read == 0)
{
throw new EndOfStreamException();
}

offset += read;
}

return buffer;
}
Expand All @@ -227,8 +262,17 @@ public virtual async Task<byte[]> ReadBytesAsync(int length, CancellationToken c

byte[] buffer = new byte[length];

if (await this._stream.ReadAsync(buffer, 0, length, cancellationToken) < length)
throw new EndOfStreamException();
int offset = 0;
while (offset < length)
{
int read = await this._stream.ReadAsync(buffer, offset, length - offset, cancellationToken);
if (read == 0)
{
throw new EndOfStreamException();
}

offset += read;
}

return buffer;
}
Expand Down Expand Up @@ -520,4 +564,4 @@ public void Dispose()
{
_stream.Dispose();
}
}
}