#region CPL License /* Nuclex Framework Copyright (C) 2002-2014 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #endregion using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; namespace Nuclex.Support.IO { /// Wraps a stream and exposes only a limited region of its data public class PartialStream : Stream { /// Initializes a new partial stream /// /// Stream the wrapper will make a limited region accessible of /// /// /// Start index in the stream which becomes the beginning for the wrapper /// /// /// Length the wrapped stream should report and allow access to /// public PartialStream(Stream stream, long start, long length) { if(start < 0) { throw new ArgumentException("Start index must not be less than 0", "start"); } if(stream.CanSeek) { if(start + length > stream.Length) { throw new ArgumentException( "Partial stream exceeds end of full stream", "length" ); } } else { if(start != 0) { throw new ArgumentException( "The only valid start for unseekable streams is 0", "start" ); } } this.stream = stream; this.start = start; this.length = length; } /// Whether data can be read from the stream public override bool CanRead { get { return this.stream.CanRead; } } /// Whether the stream supports seeking public override bool CanSeek { get { return this.stream.CanSeek; } } /// Whether data can be written into the stream public override bool CanWrite { get { return this.stream.CanWrite; } } /// /// Clears all buffers for this stream and causes any buffered data to be written /// to the underlying device. /// public override void Flush() { this.stream.Flush(); } /// Length of the stream in bytes /// /// The wrapped stream does not support seeking /// public override long Length { get { return this.length; } } /// Absolute position of the file pointer within the stream /// /// The wrapped stream does not support seeking /// public override long Position { get { if(!this.stream.CanSeek) { throw makeSeekNotSupportedException("seek"); } return this.position; } set { moveFilePointer(value); } } /// /// Reads a sequence of bytes from the stream and advances the position of /// the file pointer by the number of bytes read. /// /// Buffer that will receive the data read from the stream /// /// Offset in the buffer at which the stream will place the data read /// /// Maximum number of bytes that will be read /// /// The number of bytes that were actually read from the stream and written into /// the provided buffer /// /// /// The wrapped stream does not support reading /// public override int Read(byte[] buffer, int offset, int count) { if(!this.stream.CanRead) { throw new NotSupportedException( "Can't read: the wrapped stream doesn't support reading" ); } long remaining = this.length - this.position; int bytesToRead = (int)Math.Min(count, remaining); if(this.stream.CanSeek) { this.stream.Position = this.position + this.start; } int bytesRead = this.stream.Read(buffer, offset, bytesToRead); this.position += bytesRead; return bytesRead; } /// Changes the position of the file pointer /// /// Offset to move the file pointer by, relative to the position indicated by /// the parameter. /// /// /// Reference point relative to which the file pointer is placed /// /// The new absolute position within the stream public override long Seek(long offset, SeekOrigin origin) { switch(origin) { case SeekOrigin.Begin: { return Position = offset; } case SeekOrigin.Current: { return Position += offset; } case SeekOrigin.End: { return Position = (Length + offset); } default: { throw new ArgumentException("Invalid seek origin", "origin"); } } } /// Changes the length of the stream /// New length the stream shall have /// /// Always, the stream chainer does not support the SetLength() operation /// public override void SetLength(long value) { throw new NotSupportedException("Resizing partial streams is not supported"); } /// /// Writes a sequence of bytes to the stream and advances the position of /// the file pointer by the number of bytes written. /// /// /// Buffer containing the data that will be written to the stream /// /// /// Offset in the buffer at which the data to be written starts /// /// Number of bytes that will be written into the stream /// /// The behavior of this method is as follows: If one or more chained streams /// do not support seeking, all data is appended to the final stream in the /// chain. Otherwise, writing will begin with the stream the current file pointer /// offset falls into. If the end of that stream is reached, writing continues /// in the next stream. On the last stream, writing more data into the stream /// that it current size allows will enlarge the stream. /// public override void Write(byte[] buffer, int offset, int count) { long remaining = this.length - this.position; if(count > remaining) { throw new NotSupportedException( "Cannot extend the length of the partial stream" ); } if(this.stream.CanSeek) { this.stream.Position = this.position + this.start; } this.stream.Write(buffer, offset, count); this.position += count; } /// Stream being wrapped by the partial stream wrapper public Stream CompleteStream { get { return this.stream; } } /// Moves the file pointer /// New position the file pointer will be moved to private void moveFilePointer(long position) { if(!this.stream.CanSeek) { throw makeSeekNotSupportedException("seek"); } // Seemingly, it is okay to move the file pointer beyond the end of // the stream until you try to Read() or Write() this.position = position; } /// /// Constructs a NotSupportException for an error caused by the wrapped /// stream having no seek support /// /// Action that was tried to perform /// The newly constructed NotSupportedException private static NotSupportedException makeSeekNotSupportedException(string action) { return new NotSupportedException( string.Format( "Can't {0}: the wrapped stream does not support seeking", action ) ); } /// Streams that have been chained together private Stream stream; /// Start index of the partial stream in the wrapped stream private long start; /// Zero-based position of the partial stream's file pointer /// /// If the stream does not support seeking, the position will simply be counted /// up until it reaches . /// private long position; /// Length of the partial stream private long length; } } // namespace Nuclex.Support.IO