//
// 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");
}
}
}
}