using System; using System.IO; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; namespace PhotoThumbnail.Definitions { /// /// A wrapper for the native IStream object. /// public class StorageStream : Stream, IDisposable { private IStream _stream; private readonly bool _isReadOnly; internal StorageStream(IStream stream, bool readOnly) { _stream = stream ?? throw new ArgumentNullException("stream"); _isReadOnly = readOnly; } /// /// Reads a single byte from the stream, moving the current position ahead by 1. /// /// A single byte from the stream, -1 if end of stream. public override int ReadByte() { ThrowIfDisposed(); byte[] buffer = new byte[1]; if (Read(buffer, 0, 1) > 0) { return buffer[0]; } return -1; } /// /// Writes a single byte to the stream /// /// Byte to write to stream public override void WriteByte(byte value) { ThrowIfDisposed(); byte[] buffer = new byte[] { value }; Write(buffer, 0, 1); } /// /// Gets whether the stream can be read from. /// public override bool CanRead => _stream != null; /// /// Gets whether seeking is supported by the stream. /// public override bool CanSeek => _stream != null; /// /// Gets whether the stream can be written to. /// Always false. /// public override bool CanWrite => _stream != null && !_isReadOnly; /// /// Reads a buffer worth of bytes from the stream. /// /// Buffer to fill /// Offset to start filling in the buffer /// Number of bytes to read from the stream /// public override int Read(byte[] buffer, int offset, int count) { ThrowIfDisposed(); if (buffer == null) { throw new ArgumentNullException("buffer"); } if (offset < 0) { throw new ArgumentOutOfRangeException("offset", "StorageStreamOffsetLessThanZero"); } if (count < 0) { throw new ArgumentOutOfRangeException("count", "StorageStreamCountLessThanZero"); } if (offset + count > buffer.Length) { throw new ArgumentException("StorageStreamBufferOverflow", "count"); } int bytesRead = 0; if (count > 0) { IntPtr ptr = Marshal.AllocCoTaskMem(sizeof(ulong)); try { if (offset == 0) { _stream.Read(buffer, count, ptr); bytesRead = (int)Marshal.ReadInt64(ptr); } else { byte[] tempBuffer = new byte[count]; _stream.Read(tempBuffer, count, ptr); bytesRead = (int)Marshal.ReadInt64(ptr); if (bytesRead > 0) { Array.Copy(tempBuffer, 0, buffer, offset, bytesRead); } } } finally { Marshal.FreeCoTaskMem(ptr); } } return bytesRead; } /// /// Writes a buffer to the stream if able to do so. /// /// Buffer to write /// Offset in buffer to start writing /// Number of bytes to write to the stream public override void Write(byte[] buffer, int offset, int count) { ThrowIfDisposed(); if (_isReadOnly) { throw new InvalidOperationException("StorageStreamIsReadonly"); } if (buffer == null) { throw new ArgumentNullException("buffer"); } if (offset < 0) { throw new ArgumentOutOfRangeException("offset", "StorageStreamOffsetLessThanZero"); } if (count < 0) { throw new ArgumentOutOfRangeException("count", "StorageStreamCountLessThanZero"); } if (offset + count > buffer.Length) { throw new ArgumentException("StorageStreamBufferOverflow", "count"); } if (count > 0) { IntPtr ptr = Marshal.AllocCoTaskMem(sizeof(ulong)); try { if (offset == 0) { _stream.Write(buffer, count, ptr); } else { byte[] tempBuffer = new byte[count]; Array.Copy(buffer, offset, tempBuffer, 0, count); _stream.Write(tempBuffer, count, ptr); } } finally { Marshal.FreeCoTaskMem(ptr); } } } /// /// Gets the length of the IStream /// public override long Length { get { ThrowIfDisposed(); const int STATFLAG_NONAME = 1; _stream.Stat(out System.Runtime.InteropServices.ComTypes.STATSTG stats, STATFLAG_NONAME); return stats.cbSize; } } /// /// Gets or sets the current position within the underlying IStream. /// public override long Position { get { ThrowIfDisposed(); return Seek(0, SeekOrigin.Current); } set { ThrowIfDisposed(); Seek(value, SeekOrigin.Begin); } } /// /// Seeks within the underlying IStream. /// /// Offset /// Where to start seeking /// public override long Seek(long offset, SeekOrigin origin) { ThrowIfDisposed(); IntPtr ptr = Marshal.AllocCoTaskMem(sizeof(long)); try { _stream.Seek(offset, (int)origin, ptr); return Marshal.ReadInt64(ptr); } finally { Marshal.FreeCoTaskMem(ptr); } } /// /// Sets the length of the stream /// /// public override void SetLength(long value) { ThrowIfDisposed(); _stream.SetSize(value); } /// /// Commits data to be written to the stream if it is being cached. /// public override void Flush() { _stream.Commit((int)StorageStreamCommitOptions.None); } /// /// Disposes the stream. /// /// True if called from Dispose(), false if called from finalizer. protected override void Dispose(bool disposing) { _stream = null; base.Dispose(disposing); } private void ThrowIfDisposed() { if (_stream == null) throw new ObjectDisposedException(GetType().Name); } } /// /// Options for commiting (flushing) an IStream storage stream /// [Flags] internal enum StorageStreamCommitOptions { /// /// Uses default options /// None = 0, /// /// Overwrite option /// Overwrite = 1, /// /// Only if current /// OnlyIfCurrent = 2, /// /// Commits to disk cache dangerously /// DangerouslyCommitMerelyToDiskCache = 4, /// /// Consolidate /// Consolidate = 8 } }