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