magellan/Lucidiot.Magellan/Substream.cs

155 lines
5.6 KiB
C#

using System;
using System.IO;
namespace Lucidiot.Magellan {
/// <summary>
/// A stream based on a slice of an existing <see cref="System.Stream" />.
/// </summary>
internal class Substream : Stream {
private readonly Stream BaseStream;
private readonly long Offset;
private readonly long Limit;
// To support streams whose position cannot be accessed, we need to keep track of the stream's current position ourselves.
// We will keep the position without the offset for simpler calculations.
private long CachedPosition = 0;
public Substream(Stream baseStream, long offset)
: this(baseStream, offset, long.MaxValue) { }
public Substream(Stream baseStream, long offset, long limit) {
this.BaseStream = baseStream;
this.Offset = offset;
this.Limit = limit;
if (offset > 0) {
if (baseStream.CanSeek)
baseStream.Seek(offset, SeekOrigin.Begin);
else
throw new NotSupportedException("Cannot use a non-zero offset on streams that are not seekable.");
}
}
public override bool CanRead {
get { return BaseStream.CanRead; }
}
public override bool CanSeek {
get { return BaseStream.CanSeek; }
}
public override bool CanWrite {
get { return BaseStream.CanWrite; }
}
public override bool CanTimeout {
get { return BaseStream.CanTimeout; }
}
public override int ReadTimeout {
get {
return BaseStream.ReadTimeout;
}
set {
BaseStream.ReadTimeout = value;
}
}
public override int WriteTimeout {
get {
return BaseStream.WriteTimeout;
}
set {
BaseStream.WriteTimeout = value;
}
}
public override void Flush() {
BaseStream.Flush();
}
public override long Length {
get { return Math.Min(Limit, BaseStream.Length); }
}
public override long Position {
get {
// Do not use CachedPosition here: just let the operation fail if the stream is not supposed to support accessing its position.
return BaseStream.Position - Offset;
}
set {
if (value < 0)
throw new ArgumentOutOfRangeException("Position", "Cannot set the position to a negative value.");
if (value > Limit)
throw new EndOfStreamException("Cannot seek beyond the end of the stream.");
BaseStream.Position = value + Offset;
CachedPosition = value;
}
}
public override int Read(byte[] buffer, int offset, int count) {
int bytesRead = BaseStream.Read(buffer, offset, (int)Math.Min(Limit - CachedPosition, count));
CachedPosition += bytesRead;
return bytesRead;
}
public override int ReadByte() {
if (CachedPosition >= Limit)
return -1;
int result = BaseStream.ReadByte();
if (result >= 0)
CachedPosition += result;
return result;
}
public override long Seek(long offset, SeekOrigin origin) {
// Compute the new expected position within the slice to make boundary checks
long newSlicePosition = offset;
if (origin == SeekOrigin.Current) {
newSlicePosition += CachedPosition;
} else if (origin == SeekOrigin.End) {
newSlicePosition += Length;
}
if (newSlicePosition < 0)
throw new ArgumentOutOfRangeException("offset", "Cannot seek before the beginning of the stream.");
if (newSlicePosition > Limit)
throw new EndOfStreamException("Cannot seek beyond the end of the stream.");
// Transform the arguments to match the whole base stream.
if (origin == SeekOrigin.Begin) {
// When starting from the beginning of the stream, add the slice's offset.
offset += this.Offset;
} else if (origin == SeekOrigin.End) {
/*
* When starting from the end, since we do not know where the stream ends,
* we transform the arguments into a seek from the beginning of the stream,
* but with both the offset and the length added to the seek offset,
* causing the seek offset to actually start from the end of our slice.
*/
offset += this.Offset + this.Length;
origin = SeekOrigin.Begin;
}
BaseStream.Seek(offset, origin);
throw new NotImplementedException();
}
public override void SetLength(long value) {
throw new NotSupportedException("This stream does not support resizing.");
}
public override void Write(byte[] buffer, int offset, int count) {
count = (int)Math.Min(Limit - CachedPosition, count);
BaseStream.Write(buffer, offset, count);
CachedPosition += count;
}
public override void WriteByte(byte value) {
if (CachedPosition >= Limit)
throw new NotSupportedException("Cannot write beyond the end of the stream.");
BaseStream.WriteByte(value);
CachedPosition++;
}
}
}