// ==========================================================
// FreeImage 3 .NET wrapper
// Original FreeImage 3 functions and .NET compatible derived functions
//
// Design and implementation by
// - Jean-Philippe Goerke (jpgoerke@users.sourceforge.net)
// - Carsten Klein (cklein05@users.sourceforge.net)
//
// Contributors:
// - David Boland (davidboland@vodafone.ie)
//
// Main reference : MSDN Knowlede Base
//
// This file is part of FreeImage 3
//
// COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
// THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
// OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
// CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
// THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
// SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
// PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
// THIS DISCLAIMER.
//
// Use at your own risk!
// ==========================================================
// ==========================================================
// CVS
// $Revision: 1.6 $
// $Date: 2009/02/23 12:28:56 $
// $Id: StreamWrapper.cs,v 1.6 2009/02/23 12:28:56 cklein05 Exp $
// ==========================================================
using System;
using System.IO;
using System.Diagnostics;
namespace FreeImageAPI.IO
{
///
/// Class wrapping streams, implementing a buffer for read data,
/// so that seek operations can be made.
///
///
/// FreeImage can load bitmaps from arbitrary sources.
/// .NET works with different streams like File- or NetConnection-strams.
/// NetConnection streams, which are used to load files from web servers,
/// for example cannot seek.
/// But FreeImage frequently uses the seek operation when loading bitmaps.
/// StreamWrapper wrapps a stream and makes it seekable by caching all read
/// data into an internal MemoryStream to jump back- and forward.
/// StreamWapper is for internal use and only for loading from streams.
///
internal class StreamWrapper : Stream
{
///
/// The stream to wrap
///
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly Stream stream;
///
/// The caching stream
///
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private MemoryStream memoryStream = new MemoryStream();
///
/// Indicates if the wrapped stream reached its end
///
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private bool eos = false;
///
/// Tells the wrapper to block readings or not
///
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private bool blocking = false;
///
/// Indicates if the wrapped stream is disposed or not
///
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private bool disposed = false;
///
/// Initializes a new instance based on the specified .
///
/// The stream to wrap.
/// When true the wrapper always tries to read the requested
/// amount of data from the wrapped stream.
public StreamWrapper(Stream stream, bool blocking)
{
if (!stream.CanRead)
{
throw new ArgumentException("stream is not capable of reading.");
}
this.stream = stream;
this.blocking = blocking;
}
///
/// Releases all resources used by the instance.
///
~StreamWrapper()
{
Dispose(false);
}
// The wrapper only accepts readable streams
public override bool CanRead
{
get { checkDisposed(); return true; }
}
// We implement that feature
public override bool CanSeek
{
get { checkDisposed(); return true; }
}
// The wrapper is readonly
public override bool CanWrite
{
get { checkDisposed(); return false; }
}
// Just forward it
public override void Flush()
{
checkDisposed();
stream.Flush();
}
// Calling this property will cause the wrapper to read the stream
// to its end and cache it completely.
public override long Length
{
get
{
checkDisposed();
if (!eos)
{
Fill();
}
return memoryStream.Length;
}
}
// Gets or sets the current position
public override long Position
{
get
{
checkDisposed();
return memoryStream.Position;
}
set
{
checkDisposed();
Seek(value, SeekOrigin.Begin);
}
}
// Implements the reading feature
public override int Read(byte[] buffer, int offset, int count)
{
checkDisposed();
// total bytes read from memory-stream
int memoryBytes = 0;
// total bytes read from the original stream
int streamBytes = 0;
memoryBytes = memoryStream.Read(buffer, offset, count);
if ((count > memoryBytes) && (!eos))
{
// read the rest from the original stream (can be 0 bytes)
do
{
int read = stream.Read(
buffer,
offset + memoryBytes + streamBytes,
count - memoryBytes - streamBytes);
streamBytes += read;
if (read == 0)
{
eos = true;
break;
}
if (!blocking)
{
break;
}
} while ((memoryBytes + streamBytes) < count);
// copy the bytes from the original stream into the memory stream
// if 0 bytes were read we write 0 so the memory-stream is not changed
memoryStream.Write(buffer, offset + memoryBytes, streamBytes);
}
return memoryBytes + streamBytes;
}
// Implements the seeking feature
public override long Seek(long offset, SeekOrigin origin)
{
checkDisposed();
long newPosition = 0L;
// get new position
switch (origin)
{
case SeekOrigin.Begin:
newPosition = offset;
break;
case SeekOrigin.Current:
newPosition = memoryStream.Position + offset;
break;
case SeekOrigin.End:
// to seek from the end have have to read to the end first
if (!eos)
{
Fill();
}
newPosition = memoryStream.Length + offset;
break;
default:
throw new ArgumentOutOfRangeException("origin");
}
// in case the new position is beyond the memory-streams end
// and the original streams end hasn't been reached
// the original stream is read until either the stream ends or
// enough bytes have been read
if ((newPosition > memoryStream.Length) && (!eos))
{
memoryStream.Position = memoryStream.Length;
int bytesToRead = (int)(newPosition - memoryStream.Length);
byte[] buffer = new byte[1024];
do
{
bytesToRead -= Read(buffer, 0, (bytesToRead >= buffer.Length) ? buffer.Length : bytesToRead);
} while ((bytesToRead > 0) && (!eos));
}
memoryStream.Position = (newPosition <= memoryStream.Length) ? newPosition : memoryStream.Length;
return 0;
}
// No write-support
public override void SetLength(long value)
{
throw new Exception("The method or operation is not implemented.");
}
// No write-support
public override void Write(byte[] buffer, int offset, int count)
{
throw new Exception("The method or operation is not implemented.");
}
public void Reset()
{
checkDisposed();
Position = 0;
}
// Reads the wrapped stream until its end.
private void Fill()
{
if (!eos)
{
memoryStream.Position = memoryStream.Length;
int bytesRead = 0;
byte[] buffer = new byte[1024];
do
{
bytesRead = stream.Read(buffer, 0, buffer.Length);
memoryStream.Write(buffer, 0, bytesRead);
} while (bytesRead != 0);
eos = true;
}
}
public new void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private new void Dispose(bool disposing)
{
if (!disposed)
{
disposed = true;
if (disposing)
{
if (memoryStream != null)
{
memoryStream.Dispose();
}
}
}
}
public bool Disposed
{
get { return disposed; }
}
private void checkDisposed()
{
if (disposed) throw new ObjectDisposedException("StreamWrapper");
}
}
}