//
// © Copyright Henrik Ravn 2004
//
// Use, modification and distribution are subject to the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace DotZLib
{
///
/// Implements a compressed , in GZip (.gz) format.
///
public class GZipStream : Stream, IDisposable
{
#region Dll Imports
[DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl, CharSet=CharSet.Ansi)]
private static extern IntPtr gzopen(string name, string mode);
[DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)]
private static extern int gzclose(IntPtr gzFile);
[DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)]
private static extern int gzwrite(IntPtr gzFile, int data, int length);
[DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)]
private static extern int gzread(IntPtr gzFile, int data, int length);
[DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)]
private static extern int gzgetc(IntPtr gzFile);
[DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)]
private static extern int gzputc(IntPtr gzFile, int c);
#endregion
#region Private data
private IntPtr _gzFile;
private bool _isDisposed = false;
private bool _isWriting;
#endregion
#region Constructors
///
/// Creates a new file as a writeable GZipStream
///
/// The name of the compressed file to create
/// The compression level to use when adding data
/// If an error occurred in the internal zlib function
public GZipStream(string fileName, CompressLevel level)
{
_isWriting = true;
_gzFile = gzopen(fileName, String.Format("wb{0}", (int)level));
if (_gzFile == IntPtr.Zero)
throw new ZLibException(-1, "Could not open " + fileName);
}
///
/// Opens an existing file as a readable GZipStream
///
/// The name of the file to open
/// If an error occurred in the internal zlib function
public GZipStream(string fileName)
{
_isWriting = false;
_gzFile = gzopen(fileName, "rb");
if (_gzFile == IntPtr.Zero)
throw new ZLibException(-1, "Could not open " + fileName);
}
#endregion
#region Access properties
///
/// Returns true of this stream can be read from, false otherwise
///
public override bool CanRead
{
get
{
return !_isWriting;
}
}
///
/// Returns false.
///
public override bool CanSeek
{
get
{
return false;
}
}
///
/// Returns true if this tsream is writeable, false otherwise
///
public override bool CanWrite
{
get
{
return _isWriting;
}
}
#endregion
#region Destructor & IDispose stuff
///
/// Destroys this instance
///
~GZipStream()
{
cleanUp(false);
}
///
/// Closes the external file handle
///
public void Dispose()
{
cleanUp(true);
}
// Does the actual closing of the file handle.
private void cleanUp(bool isDisposing)
{
if (!_isDisposed)
{
gzclose(_gzFile);
_isDisposed = true;
}
}
#endregion
#region Basic reading and writing
///
/// Attempts to read a number of bytes from the stream.
///
/// The destination data buffer
/// The index of the first destination byte in buffer
/// The number of bytes requested
/// The number of bytes read
/// If buffer is null
/// If count or offset are negative
/// If offset + count is > buffer.Length
/// If this stream is not readable.
/// If this stream has been disposed.
public override int Read(byte[] buffer, int offset, int count)
{
if (!CanRead) throw new NotSupportedException();
if (buffer == null) throw new ArgumentNullException();
if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException();
if ((offset+count) > buffer.Length) throw new ArgumentException();
if (_isDisposed) throw new ObjectDisposedException("GZipStream");
GCHandle h = GCHandle.Alloc(buffer, GCHandleType.Pinned);
int result;
try
{
result = gzread(_gzFile, h.AddrOfPinnedObject().ToInt32() + offset, count);
if (result < 0)
throw new IOException();
}
finally
{
h.Free();
}
return result;
}
///
/// Attempts to read a single byte from the stream.
///
/// The byte that was read, or -1 in case of error or End-Of-File
public override int ReadByte()
{
if (!CanRead) throw new NotSupportedException();
if (_isDisposed) throw new ObjectDisposedException("GZipStream");
return gzgetc(_gzFile);
}
///
/// Writes a number of bytes to the stream
///
///
///
///
/// If buffer is null
/// If count or offset are negative
/// If offset + count is > buffer.Length
/// If this stream is not writeable.
/// If this stream has been disposed.
public override void Write(byte[] buffer, int offset, int count)
{
if (!CanWrite) throw new NotSupportedException();
if (buffer == null) throw new ArgumentNullException();
if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException();
if ((offset+count) > buffer.Length) throw new ArgumentException();
if (_isDisposed) throw new ObjectDisposedException("GZipStream");
GCHandle h = GCHandle.Alloc(buffer, GCHandleType.Pinned);
try
{
int result = gzwrite(_gzFile, h.AddrOfPinnedObject().ToInt32() + offset, count);
if (result < 0)
throw new IOException();
}
finally
{
h.Free();
}
}
///
/// Writes a single byte to the stream
///
/// The byte to add to the stream.
/// If this stream is not writeable.
/// If this stream has been disposed.
public override void WriteByte(byte value)
{
if (!CanWrite) throw new NotSupportedException();
if (_isDisposed) throw new ObjectDisposedException("GZipStream");
int result = gzputc(_gzFile, (int)value);
if (result < 0)
throw new IOException();
}
#endregion
#region Position & length stuff
///
/// Not supported.
///
///
/// Always thrown
public override void SetLength(long value)
{
throw new NotSupportedException();
}
///
/// Not supported.
///
///
///
///
/// Always thrown
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
///
/// Flushes the GZipStream.
///
/// In this implementation, this method does nothing. This is because excessive
/// flushing may degrade the achievable compression rates.
public override void Flush()
{
// left empty on purpose
}
///
/// Gets/sets the current position in the GZipStream. Not supported.
///
/// In this implementation this property is not supported
/// Always thrown
public override long Position
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}
///
/// Gets the size of the stream. Not supported.
///
/// In this implementation this property is not supported
/// Always thrown
public override long Length
{
get
{
throw new NotSupportedException();
}
}
#endregion
}
}