Skip to content

Instantly share code, notes, and snippets.

@AyrA
Created March 10, 2023 18:12
Show Gist options
  • Save AyrA/b709e5fcc6a4175063a2aea26e48f301 to your computer and use it in GitHub Desktop.
Save AyrA/b709e5fcc6a4175063a2aea26e48f301 to your computer and use it in GitHub Desktop.
Stream that is a window of a bigger stream. Allows to only give parts of a stream to a component
using System;
using System.IO;
namespace AyrA.IO
{
public class WindowedStream : Stream
{
public override bool CanRead => baseStream.CanRead;
public override bool CanSeek => baseStream.CanSeek;
public override bool CanWrite => false;
public override long Length => length;
public override long Position
{
get => baseOffset;
set => Seek(value, SeekOrigin.Begin);
}
private readonly Stream baseStream;
private long length;
private readonly bool keepOpen;
private long baseOffset = 0;
public WindowedStream(Stream baseStream, long length, bool keepOpen = false)
{
this.baseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream));
if (length < 0)
{
throw new ArgumentOutOfRangeException(nameof(length), "Length cannot be negative");
}
//Validate length if it's possible
try
{
if (length > baseStream.Length - baseStream.Position)
{
throw new ArgumentOutOfRangeException(nameof(length), "Length exceeds remaining data in base stream");
}
}
catch
{
//NOOP
}
this.length = length;
this.keepOpen = keepOpen;
}
public override void Flush()
{
throw new NotSupportedException("Base stream is not writable");
}
public override int Read(byte[] buffer, int offset, int count)
{
var maxRead = (int)Math.Min(int.MaxValue, length - baseOffset);
int read = baseStream.Read(buffer, offset, Math.Min(maxRead, count));
baseOffset += read;
return read;
}
public override long Seek(long offset, SeekOrigin origin)
{
if (!CanSeek)
{
throw new NotSupportedException("Base stream is not seekable");
}
var pos = origin switch
{
SeekOrigin.Begin => offset,
SeekOrigin.Current => baseOffset + offset,
SeekOrigin.End => length + offset,
_ => throw new ArgumentOutOfRangeException(nameof(origin)),
};
//Pos now contains the new absolute position
//We check if this is still inside of the permitted window
if (pos < 0 || pos > length)
{
throw new ArgumentOutOfRangeException(nameof(offset), "New position is outside of the possible range");
}
baseStream.Seek(pos - baseOffset, SeekOrigin.Current);
return baseOffset = pos;
}
public override void SetLength(long value)
{
if (baseOffset > value)
{
throw new ArgumentOutOfRangeException(nameof(baseOffset), "Cannot reduce length beyond current position");
}
length = value;
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException("Base stream is not writable");
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!keepOpen)
{
baseStream.Dispose();
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment