// // Copyright (c) 2008-2011, Kenneth Bell // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // using System; using System.Collections.Generic; using System.IO; namespace DiscUtils.Streams { /// /// A wrapper stream that enables you to take a snapshot, pushing changes into a side buffer. /// /// Once a snapshot is taken, you can discard subsequent changes or merge them back /// into the wrapped stream. public sealed class SnapshotStream : SparseStream { private Stream _baseStream; private readonly Ownership _baseStreamOwnership; /// /// Records which byte ranges in diffStream hold changes. /// /// Can't use _diffStream's own tracking because that's based on it's /// internal block size, not on the _actual_ bytes stored. private List _diffExtents; /// /// Captures changes to the base stream (when enabled). /// private SparseMemoryStream _diffStream; /// /// Indicates that no writes should be permitted. /// private bool _frozen; private long _position; /// /// The saved stream position (if the diffStream is active). /// private long _savedPosition; /// /// Initializes a new instance of the SnapshotStream class. /// /// The stream to wrap. /// Indicates if this stream should control the lifetime of baseStream. public SnapshotStream(Stream baseStream, Ownership owns) { _baseStream = baseStream; _baseStreamOwnership = owns; _diffExtents = new List(); } /// /// Gets an indication as to whether the stream can be read. /// public override bool CanRead { get { return _baseStream.CanRead; } } /// /// Gets an indication as to whether the stream position can be changed. /// public override bool CanSeek { get { return _baseStream.CanSeek; } } /// /// Gets an indication as to whether the stream can be written to. /// /// This property is orthogonal to Freezing/Thawing, it's /// perfectly possible for a stream to be frozen and this method /// return true. public override bool CanWrite { get { return _diffStream != null ? true : _baseStream.CanWrite; } } /// /// Returns an enumeration over the parts of the stream that contain real data. /// public override IEnumerable Extents { get { SparseStream sparseBase = _baseStream as SparseStream; if (sparseBase == null) { return new[] { new StreamExtent(0, Length) }; } return StreamExtent.Union(sparseBase.Extents, _diffExtents); } } /// /// Gets the length of the stream. /// public override long Length { get { if (_diffStream != null) { return _diffStream.Length; } return _baseStream.Length; } } /// /// Gets and sets the current stream position. /// public override long Position { get { return _position; } set { _position = value; } } /// /// Prevents any write operations to the stream. /// /// Useful to prevent changes whilst inspecting the stream. public void Freeze() { _frozen = true; } /// /// Re-permits write operations to the stream. /// public void Thaw() { _frozen = false; } /// /// Takes a snapshot of the current stream contents. /// public void Snapshot() { if (_diffStream != null) { throw new InvalidOperationException("Already have a snapshot"); } _savedPosition = _position; _diffExtents = new List(); _diffStream = new SparseMemoryStream(); _diffStream.SetLength(_baseStream.Length); } /// /// Reverts to a previous snapshot, discarding any changes made to the stream. /// public void RevertToSnapshot() { if (_diffStream == null) { throw new InvalidOperationException("No snapshot"); } _diffStream = null; _diffExtents = null; _position = _savedPosition; } /// /// Discards the snapshot any changes made after the snapshot was taken are kept. /// public void ForgetSnapshot() { if (_diffStream == null) { throw new InvalidOperationException("No snapshot"); } byte[] buffer = new byte[8192]; foreach (StreamExtent extent in _diffExtents) { _diffStream.Position = extent.Start; _baseStream.Position = extent.Start; int totalRead = 0; while (totalRead < extent.Length) { int toRead = (int)Math.Min(extent.Length - totalRead, buffer.Length); int read = _diffStream.Read(buffer, 0, toRead); _baseStream.Write(buffer, 0, read); totalRead += read; } } _diffStream = null; _diffExtents = null; } /// /// Flushes the stream. /// public override void Flush() { CheckFrozen(); _baseStream.Flush(); } /// /// Reads data from the stream. /// /// The buffer to fill. /// The buffer offset to start from. /// The number of bytes to read. /// The number of bytes read. public override int Read(byte[] buffer, int offset, int count) { int numRead; if (_diffStream == null) { _baseStream.Position = _position; numRead = _baseStream.Read(buffer, offset, count); } else { if (_position > _diffStream.Length) { throw new IOException("Attempt to read beyond end of file"); } int toRead = (int)Math.Min(count, _diffStream.Length - _position); // If the read is within the base stream's range, then touch it first to get the // (potentially) stale data. if (_position < _baseStream.Length) { int baseToRead = (int)Math.Min(toRead, _baseStream.Length - _position); _baseStream.Position = _position; int totalBaseRead = 0; while (totalBaseRead < baseToRead) { totalBaseRead += _baseStream.Read(buffer, offset + totalBaseRead, baseToRead - totalBaseRead); } } // Now overlay any data from the overlay stream (if any) IEnumerable overlayExtents = StreamExtent.Intersect(_diffExtents, new StreamExtent(_position, toRead)); foreach (StreamExtent extent in overlayExtents) { _diffStream.Position = extent.Start; int overlayNumRead = 0; while (overlayNumRead < extent.Length) { overlayNumRead += _diffStream.Read( buffer, (int)(offset + (extent.Start - _position) + overlayNumRead), (int)(extent.Length - overlayNumRead)); } } numRead = toRead; } _position += numRead; return numRead; } /// /// Moves the stream position. /// /// The origin-relative location. /// The base location. /// The new absolute stream position. public override long Seek(long offset, SeekOrigin origin) { CheckFrozen(); long effectiveOffset = offset; if (origin == SeekOrigin.Current) { effectiveOffset += _position; } else if (origin == SeekOrigin.End) { effectiveOffset += Length; } if (effectiveOffset < 0) { throw new IOException("Attempt to move before beginning of disk"); } _position = effectiveOffset; return _position; } /// /// Sets the length of the stream. /// /// The new length. public override void SetLength(long value) { CheckFrozen(); if (_diffStream != null) { _diffStream.SetLength(value); } else { _baseStream.SetLength(value); } } /// /// Writes data to the stream at the current location. /// /// The data to write. /// The first byte to write from buffer. /// The number of bytes to write. public override void Write(byte[] buffer, int offset, int count) { CheckFrozen(); if (_diffStream != null) { _diffStream.Position = _position; _diffStream.Write(buffer, offset, count); // Beware of Linq's delayed model - force execution now by placing into a list. // Without this, large execution chains can build up (v. slow) and potential for stack overflow. _diffExtents = new List(StreamExtent.Union(_diffExtents, new StreamExtent(_position, count))); _position += count; } else { _baseStream.Position = _position; _baseStream.Write(buffer, offset, count); _position += count; } } /// /// Disposes of this instance. /// /// true if called from Dispose(), else false. protected override void Dispose(bool disposing) { if (disposing) { if (_baseStreamOwnership == Ownership.Dispose && _baseStream != null) { _baseStream.Dispose(); } _baseStream = null; if (_diffStream != null) { _diffStream.Dispose(); } _diffStream = null; } base.Dispose(disposing); } private void CheckFrozen() { if (_frozen) { throw new InvalidOperationException("The stream is frozen"); } } } }