using System; using System.IO; namespace Packaging.Targets.IO { /// /// Represents parts of a , from a start byte offset for a given length. /// public class SubStream : Stream { /// /// The parent stream of this stream. /// private Stream stream; /// /// The offset, that is, the location at which the starts. /// private long subStreamOffset; /// /// The length of the . /// private long subStreamLength; /// /// A value indicating whether the parent stream should be closed when this /// is closed, or not. /// private bool leaveParentOpen; /// /// A value indicating whether this should only support read-only operations. /// private bool readOnly; /// /// The current position of the . This allows us keep the /// consistent accross multiple calls, even if the position of changes (e.g. because /// another operates on it). /// private long position; /// /// Initializes a new instance of the class. /// /// /// The parent stream of this stream. /// /// /// The offset at which the stream starts. /// /// /// The length of the . /// /// /// A value indicating whether the parent stream should be closed when this /// is closed, or not. /// /// /// A value indicating whether the be opened in read-only mode or not. /// public SubStream(Stream stream, long offset, long length, bool leaveParentOpen = false, bool readOnly = false) { this.stream = stream; this.subStreamOffset = offset; this.subStreamLength = length; this.leaveParentOpen = leaveParentOpen; this.readOnly = readOnly; this.position = 0; if (this.stream.CanSeek) { this.Seek(0, SeekOrigin.Begin); } } /// /// Gets a value indicating whether the current stream supports reading /// public override bool CanRead { get { return this.stream.CanRead; } } /// /// Gets a value indicating whether the current stream supports seeking. /// public override bool CanSeek { get { return this.stream.CanSeek; } } /// /// Gets a value indicating whether the current stream supports writing. /// public override bool CanWrite { get { return !this.readOnly && this.stream.CanWrite; } } /// /// Gets the length in bytes of the stream. /// public override long Length { get { return this.subStreamLength; } } /// /// Gets or sets the position within the current . /// public override long Position { get { return this.position; } set { lock (this.stream) { this.stream.Position = value + this.Offset; this.position = value; } } } /// /// Gets the parent stream of this . /// internal Stream Stream { get { return this.stream; } } /// /// Gets the offset at which the starts. /// internal long Offset { get { return this.subStreamOffset; } } /// /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. /// public override void Flush() { lock (this.stream) { this.stream.Flush(); } } /// /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. /// /// /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset /// and (offset + count - 1) replaced by the bytes read from the current source. /// /// /// The zero-based byte offset in buffer at which to begin storing the data read from the current stream. /// /// /// The maximum number of bytes to be read from the current stream. /// /// /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are /// not currently available, or zero (0) if the end of the stream has been reached. /// public override int Read(byte[] buffer, int offset, int count) { lock (this.stream) { this.EnsurePosition(); // Make sure we don't pass the size of the substream long bytesRemaining = this.Length - this.Position; long bytesToRead = Math.Min(count, bytesRemaining); if (bytesToRead < 0) { bytesToRead = 0; } var read = this.stream.Read(buffer, offset, (int)bytesToRead); this.position += read; return read; } } /// /// Writes a sequence of bytes to the current stream and advances the current position /// within this stream by the number of bytes written. /// /// /// An array of bytes. This method copies count bytes from buffer to the current stream. /// /// /// The zero-based byte offset in buffer at which to begin copying bytes to the current stream. /// /// /// The number of bytes to be written to the current stream. /// public override void Write(byte[] buffer, int offset, int count) { if (this.readOnly) { throw new NotSupportedException(); } lock (this.stream) { this.EnsurePosition(); if (this.Position + offset + count > this.Length || this.Position < 0) { throw new InvalidOperationException("This write operation would exceed the current length of the substream."); } this.stream.Write(buffer, offset, count); this.position += count; } } /// /// Writes a byte to the current position in the stream and advances the position within the stream by one byte. /// /// /// The byte to write to the stream. /// public override void WriteByte(byte value) { if (this.readOnly) { throw new NotSupportedException(); } lock (this.stream) { this.EnsurePosition(); if (this.Position > this.Length || this.Position < 0) { throw new InvalidOperationException("This write operation would exceed the current length of the substream."); } this.stream.WriteByte(value); this.position++; } } /// /// Sets the position within the current stream. /// /// /// A byte offset relative to the origin parameter. /// /// /// A value of type SeekOrigin indicating the reference point used to obtain the new position. /// /// /// The new position within the current stream. /// public override long Seek(long offset, SeekOrigin origin) { lock (this.stream) { switch (origin) { case SeekOrigin.Begin: offset += this.subStreamOffset; break; case SeekOrigin.End: long enddelta = this.subStreamOffset + this.subStreamLength - this.stream.Length; offset += enddelta; break; case SeekOrigin.Current: // Nothing to do, because we'll pass SeekOrigin.Current to the // parent stream. break; } // If we're doing an absolute seek, we don't care about the position, // but if the seek is relative, make sure we start from the correct position if (origin == SeekOrigin.Current) { this.EnsurePosition(); } var parentPosition = this.stream.Seek(offset, origin); this.position = parentPosition - this.Offset; return this.position; } } /// /// Sets the length of the current stream. /// /// /// The desired length of the current stream in bytes. /// public override void SetLength(long value) { if (this.readOnly) { throw new NotSupportedException(); } this.subStreamLength = value; } /// /// Updates the size of this relative to its parent stream. /// /// /// The new offset. /// /// /// The new length. /// public void UpdateWindow(long offset, long length) { this.subStreamOffset = offset; this.subStreamLength = length; } /// protected override void Dispose(bool disposing) { if (!this.leaveParentOpen) { this.stream.Dispose(); } base.Dispose(disposing); } /// /// Makes sure the position of the parent stream is aligned with the position we have stored locally. /// /// /// Take a scenario where you have two objects that navigate the same . /// They can both seek independently, so the parent's position will change without the other /// knowing about it. Calling corrects that, enabling scenarios where you can synchronously /// do I/O on both streams: things like should start working. /// This will, however, not work for multi-threaded access. /// private void EnsurePosition() { if (this.stream.Position != this.position + this.Offset) { this.stream.Position = this.position + this.Offset; } } } }