155 lines
5.6 KiB
C#
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++;
|
|
}
|
|
}
|
|
} |