#region Copyright 2011-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion
using System;
using System.IO;
namespace CSharpTest.Net.IO
{
///
/// Provides a stream that clamps the usage of the input stream to a specific range, length or offset
///
public class ClampedStream : Stream
{
private readonly Stream _rawStream;
private readonly bool _disposeOfStream;
private readonly long _startPosition;
private readonly long _limitPosition;
private bool _disposed;
private long _current;
///
/// Creates a stream that limits the users ability to modify data to the specified range
///
/// The stream to use for read/write
/// The position in the stream that should start the range of allowed bytes
/// The maximum length that can be read from the stream
public ClampedStream(Stream rawStream, long start, long length)
: this(rawStream, start, length, true) { }
///
/// Creates a stream that limits the users ability to modify data to the specified range
///
/// The stream to use for read/write
/// The position in the stream that should start the range of allowed bytes
/// The maximum length that can be read from the stream
/// True to dispose of the rawStream when this stream is disposed
public ClampedStream(Stream rawStream, long start, long length, bool disposeOfStream)
{
_rawStream = rawStream;
_disposeOfStream = disposeOfStream;
_startPosition = start;
Check.InRange(start, 0, long.MaxValue);
Check.InRange(length, 0, long.MaxValue);
if (length < long.MaxValue)
{
if (((ulong)start) + ((ulong)length) > long.MaxValue)
throw new ArgumentOutOfRangeException();
_limitPosition = start + length;
}
else _limitPosition = long.MaxValue;
if (_rawStream.CanSeek)
_current = _rawStream.Seek(_startPosition, SeekOrigin.Begin);
else if (_startPosition > 0)
throw new ArgumentException("Unable to seek to the offset required.");
}
///
/// Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream.
///
public override void Close()
{
if(_disposeOfStream)
_rawStream.Close();
base.Close();
}
///
/// Releases the unmanaged resources used by the and optionally releases the managed resources.
///
protected override void Dispose(bool disposing)
{
if (!_disposed)
{
_disposed = true;
if (disposing && _disposeOfStream)
_rawStream.Dispose();
}
base.Dispose(disposing);
}
private void IsNotDisposed()
{
if (_disposed) throw new ObjectDisposedException(GetType().FullName);
}
///
/// When overridden in a derived class, gets a value indicating whether the current stream supports reading.
///
public override bool CanRead { get { return _rawStream.CanRead; } }
///
/// When overridden in a derived class, gets a value indicating whether the current stream supports seeking.
///
public override bool CanSeek { get { return _rawStream.CanSeek; } }
///
/// When overridden in a derived class, gets a value indicating whether the current stream supports writing.
///
public override bool CanWrite { get { return _rawStream.CanWrite; } }
///
/// When overridden in a derived class, clears all buffers for this stream and causes any buffered data to be written to the underlying device.
///
public override void Flush()
{
IsNotDisposed();
if(_rawStream.CanWrite)
_rawStream.Flush();
}
///
/// When overridden in a derived class, sets the position within the current stream.
///
public override long Seek(long offset, SeekOrigin origin)
{
IsNotDisposed();
if (origin == SeekOrigin.Begin)
offset += _startPosition;
else if (origin == SeekOrigin.Current)
offset += _rawStream.Position;
else if (origin == SeekOrigin.End)
offset += Math.Min(_limitPosition, _rawStream.Length);
else
throw new ArgumentOutOfRangeException("origin");
if (offset < _startPosition)
{
_rawStream.Seek(-1, SeekOrigin.Begin);
throw new ArgumentOutOfRangeException("offset");
}
if (offset > _limitPosition)
throw new ArgumentOutOfRangeException("offset", "Attempt to seek past end of stream.");
_current = _rawStream.Seek(offset, SeekOrigin.Begin);
return _current - _startPosition;
}
///
/// When overridden in a derived class, sets the length of the current stream.
///
/// Thrown when length != long.MaxValue
public override void SetLength(long value)
{
IsNotDisposed();
Check.Assert(_limitPosition == long.MaxValue);
Check.InRange(value, 0, _limitPosition - _startPosition);
value += _startPosition;
value = Math.Min(_limitPosition, value);
_rawStream.SetLength(value);
}
///
/// When overridden in a derived class, gets the length in bytes of the stream.
///
public override long Length
{
get { return Math.Max(0, Math.Min(_limitPosition, _rawStream.Length) - _startPosition); }
}
///
/// When overridden in a derived class, gets or sets the position within the current stream.
///
public override long Position
{
get { IsNotDisposed(); return _current - _startPosition; }
set { IsNotDisposed(); Seek(value, SeekOrigin.Begin); }
}
///
/// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
///
public override int Read(byte[] buffer, int offset, int count)
{
IsNotDisposed();
Check.Assert(CanRead);
long remaining = _limitPosition - _current;
if (remaining < count)
count = (int)remaining;
if (count <= 0)
return 0;
int amt = _rawStream.Read(buffer, offset, count);
_current += amt;
return amt;
}
///
/// Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream.
///
public override int ReadByte()
{
IsNotDisposed();
Check.Assert(CanRead);
long remaining = _limitPosition - _current;
if (remaining < 1)
return -1;
int result = _rawStream.ReadByte();
_current++;
return result;
}
///
/// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
///
public override void Write(byte[] buffer, int offset, int count)
{
IsNotDisposed();
Check.Assert(CanWrite);
long remaining = _limitPosition - _current;
if (remaining < count)
throw new InvalidOperationException("Attempt to write past end of stream.");
_rawStream.Write(buffer, offset, count);
_current += count;
}
///
/// Writes a byte to the current position in the stream and advances the position within the stream by one byte.
///
public override void WriteByte(byte value)
{
IsNotDisposed();
Check.Assert(CanWrite);
long remaining = _limitPosition - _current;
if (remaining < 1)
throw new InvalidOperationException("Attempt to write past end of stream.");
base.WriteByte(value);
_current++;
}
}
}