Skip to content
Draft
119 changes: 119 additions & 0 deletions src/ImageSharp/Compression/Zlib/ChunkedReadStream.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

using SixLabors.ImageSharp.IO;

namespace SixLabors.ImageSharp.Compression.Zlib;

/// <summary>
/// A read-only stream over a sequence of length-delimited segments. Bytes are
/// pulled from the inner stream up to the current segment's remaining length;
/// when the segment is exhausted the supplied delegate is invoked to advance
/// to the next segment and return its length. The inner stream is not owned
/// and is not disposed.
/// </summary>
internal sealed class ChunkedReadStream : Stream
{
private static readonly Func<int> GetDataNoOp = () => 0;

private readonly BufferedReadStream innerStream;
private readonly Func<int> getData;
private int currentDataRemaining;

public ChunkedReadStream(BufferedReadStream innerStream)
: this(innerStream, GetDataNoOp)
{
}

public ChunkedReadStream(BufferedReadStream innerStream, Func<int> getData)
{
this.innerStream = innerStream;
this.getData = getData;
}

/// <inheritdoc/>
public override bool CanRead => this.innerStream.CanRead;

/// <inheritdoc/>
public override bool CanSeek => false;

/// <inheritdoc/>
public override bool CanWrite => throw new NotSupportedException();

/// <inheritdoc/>
public override long Length => throw new NotSupportedException();

/// <inheritdoc/>
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }

/// <summary>
/// Sets the number of bytes available to read from the current segment.
/// Must be called before reading each segment.
/// </summary>
public void SetCurrentSegmentLength(int bytes) => this.currentDataRemaining = bytes;

/// <inheritdoc/>
public override void Flush() => throw new NotSupportedException();

/// <inheritdoc/>
public override int ReadByte()
{
if (this.currentDataRemaining is 0)
{
this.currentDataRemaining = this.getData();
if (this.currentDataRemaining is 0)
{
return -1;
}
}

int value = this.innerStream.ReadByte();
if (value is not -1)
{
this.currentDataRemaining--;
}

return value;
}

/// <inheritdoc/>
public override int Read(byte[] buffer, int offset, int count)
{
// Decrement currentDataRemaining only by bytes actually returned by
// innerStream.Read; a short read otherwise underflows the segment
// counter and triggers getData() before the segment is truly drained.
int totalBytesRead = 0;
while (totalBytesRead < count)
{
if (this.currentDataRemaining is 0)
{
this.currentDataRemaining = this.getData();
if (this.currentDataRemaining is 0)
{
break;
}
}

int bytesToRead = Math.Min(count - totalBytesRead, this.currentDataRemaining);
int bytesRead = this.innerStream.Read(buffer, offset + totalBytesRead, bytesToRead);
if (bytesRead is 0)
{
break;
}

this.currentDataRemaining -= bytesRead;
totalBytesRead += bytesRead;
}

return totalBytesRead;
}

/// <inheritdoc/>
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();

/// <inheritdoc/>
public override void SetLength(long value) => throw new NotSupportedException();

/// <inheritdoc/>
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
}
Loading
Loading