1 //
   2 // © Copyright Henrik Ravn 2004
   3 //
   4 // Use, modification and distribution are subject to the Boost Software License, Version 1.0.
   5 // (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
   6 //
   7 
   8 using System;
   9 using System.IO;
  10 using System.Runtime.InteropServices;
  11 
  12 namespace DotZLib
  13 {
  14         /// <summary>
  15         /// Implements a compressed <see cref="Stream"/>, in GZip (.gz) format.
  16         /// </summary>
  17         public class GZipStream : Stream, IDisposable
  18         {
  19         #region Dll Imports
  20         [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl, CharSet=CharSet.Ansi)]
  21         private static extern IntPtr gzopen(string name, string mode);
  22 
  23         [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)]
  24         private static extern int gzclose(IntPtr gzFile);
  25 
  26         [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)]
  27         private static extern int gzwrite(IntPtr gzFile, int data, int length);
  28 
  29         [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)]
  30         private static extern int gzread(IntPtr gzFile, int data, int length);
  31 
  32         [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)]
  33         private static extern int gzgetc(IntPtr gzFile);
  34 
  35         [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)]
  36         private static extern int gzputc(IntPtr gzFile, int c);
  37 
  38         #endregion
  39 
  40         #region Private data
  41         private IntPtr _gzFile;
  42         private bool _isDisposed = false;
  43         private bool _isWriting;
  44         #endregion
  45 
  46         #region Constructors
  47         /// <summary>
  48         /// Creates a new file as a writeable GZipStream
  49         /// </summary>
  50         /// <param name="fileName">The name of the compressed file to create</param>
  51         /// <param name="level">The compression level to use when adding data</param>
  52         /// <exception cref="ZLibException">If an error occurred in the internal zlib function</exception>
  53                 public GZipStream(string fileName, CompressLevel level)
  54                 {
  55             _isWriting = true;
  56             _gzFile = gzopen(fileName, String.Format("wb{0}", (int)level));
  57             if (_gzFile == IntPtr.Zero)
  58                 throw new ZLibException(-1, "Could not open " + fileName);
  59                 }
  60 
  61         /// <summary>
  62         /// Opens an existing file as a readable GZipStream
  63         /// </summary>
  64         /// <param name="fileName">The name of the file to open</param>
  65         /// <exception cref="ZLibException">If an error occurred in the internal zlib function</exception>
  66         public GZipStream(string fileName)
  67         {
  68             _isWriting = false;
  69             _gzFile = gzopen(fileName, "rb");
  70             if (_gzFile == IntPtr.Zero)
  71                 throw new ZLibException(-1, "Could not open " + fileName);
  72 
  73         }
  74         #endregion
  75 
  76         #region Access properties
  77         /// <summary>
  78         /// Returns true of this stream can be read from, false otherwise
  79         /// </summary>
  80         public override bool CanRead
  81         {
  82             get
  83             {
  84                 return !_isWriting;
  85             }
  86         }
  87 
  88 
  89         /// <summary>
  90         /// Returns false.
  91         /// </summary>
  92         public override bool CanSeek
  93         {
  94             get
  95             {
  96                 return false;
  97             }
  98         }
  99 
 100         /// <summary>
 101         /// Returns true if this tsream is writeable, false otherwise
 102         /// </summary>
 103         public override bool CanWrite
 104         {
 105             get
 106             {
 107                 return _isWriting;
 108             }
 109         }
 110         #endregion
 111 
 112         #region Destructor & IDispose stuff
 113 
 114         /// <summary>
 115         /// Destroys this instance
 116         /// </summary>
 117         ~GZipStream()
 118         {
 119             cleanUp(false);
 120         }
 121 
 122         /// <summary>
 123         /// Closes the external file handle
 124         /// </summary>
 125         public void Dispose()
 126         {
 127             cleanUp(true);
 128         }
 129 
 130         // Does the actual closing of the file handle.
 131         private void cleanUp(bool isDisposing)
 132         {
 133             if (!_isDisposed)
 134             {
 135                 gzclose(_gzFile);
 136                 _isDisposed = true;
 137             }
 138         }
 139         #endregion
 140 
 141         #region Basic reading and writing
 142         /// <summary>
 143         /// Attempts to read a number of bytes from the stream.
 144         /// </summary>
 145         /// <param name="buffer">The destination data buffer</param>
 146         /// <param name="offset">The index of the first destination byte in <c>buffer</c></param>
 147         /// <param name="count">The number of bytes requested</param>
 148         /// <returns>The number of bytes read</returns>
 149         /// <exception cref="ArgumentNullException">If <c>buffer</c> is null</exception>
 150         /// <exception cref="ArgumentOutOfRangeException">If <c>count</c> or <c>offset</c> are negative</exception>
 151         /// <exception cref="ArgumentException">If <c>offset</c>  + <c>count</c> is &gt; buffer.Length</exception>
 152         /// <exception cref="NotSupportedException">If this stream is not readable.</exception>
 153         /// <exception cref="ObjectDisposedException">If this stream has been disposed.</exception>
 154         public override int Read(byte[] buffer, int offset, int count)
 155         {
 156             if (!CanRead) throw new NotSupportedException();
 157             if (buffer == null) throw new ArgumentNullException();
 158             if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException();
 159             if ((offset+count) > buffer.Length) throw new ArgumentException();
 160             if (_isDisposed) throw new ObjectDisposedException("GZipStream");
 161 
 162             GCHandle h = GCHandle.Alloc(buffer, GCHandleType.Pinned);
 163             int result;
 164             try
 165             {
 166                 result = gzread(_gzFile, h.AddrOfPinnedObject().ToInt32() + offset, count);
 167                 if (result < 0)
 168                     throw new IOException();
 169             }
 170             finally
 171             {
 172                 h.Free();
 173             }
 174             return result;
 175         }
 176 
 177         /// <summary>
 178         /// Attempts to read a single byte from the stream.
 179         /// </summary>
 180         /// <returns>The byte that was read, or -1 in case of error or End-Of-File</returns>
 181         public override int ReadByte()
 182         {
 183             if (!CanRead) throw new NotSupportedException();
 184             if (_isDisposed) throw new ObjectDisposedException("GZipStream");
 185             return gzgetc(_gzFile);
 186         }
 187 
 188         /// <summary>
 189         /// Writes a number of bytes to the stream
 190         /// </summary>
 191         /// <param name="buffer"></param>
 192         /// <param name="offset"></param>
 193         /// <param name="count"></param>
 194         /// <exception cref="ArgumentNullException">If <c>buffer</c> is null</exception>
 195         /// <exception cref="ArgumentOutOfRangeException">If <c>count</c> or <c>offset</c> are negative</exception>
 196         /// <exception cref="ArgumentException">If <c>offset</c>  + <c>count</c> is &gt; buffer.Length</exception>
 197         /// <exception cref="NotSupportedException">If this stream is not writeable.</exception>
 198         /// <exception cref="ObjectDisposedException">If this stream has been disposed.</exception>
 199         public override void Write(byte[] buffer, int offset, int count)
 200         {
 201             if (!CanWrite) throw new NotSupportedException();
 202             if (buffer == null) throw new ArgumentNullException();
 203             if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException();
 204             if ((offset+count) > buffer.Length) throw new ArgumentException();
 205             if (_isDisposed) throw new ObjectDisposedException("GZipStream");
 206 
 207             GCHandle h = GCHandle.Alloc(buffer, GCHandleType.Pinned);
 208             try
 209             {
 210                 int result = gzwrite(_gzFile, h.AddrOfPinnedObject().ToInt32() + offset, count);
 211                 if (result < 0)
 212                     throw new IOException();
 213             }
 214             finally
 215             {
 216                 h.Free();
 217             }
 218         }
 219 
 220         /// <summary>
 221         /// Writes a single byte to the stream
 222         /// </summary>
 223         /// <param name="value">The byte to add to the stream.</param>
 224         /// <exception cref="NotSupportedException">If this stream is not writeable.</exception>
 225         /// <exception cref="ObjectDisposedException">If this stream has been disposed.</exception>
 226         public override void WriteByte(byte value)
 227         {
 228             if (!CanWrite) throw new NotSupportedException();
 229             if (_isDisposed) throw new ObjectDisposedException("GZipStream");
 230 
 231             int result = gzputc(_gzFile, (int)value);
 232             if (result < 0)
 233                 throw new IOException();
 234         }
 235         #endregion
 236 
 237         #region Position & length stuff
 238         /// <summary>
 239         /// Not supported.
 240         /// </summary>
 241         /// <param name="value"></param>
 242         /// <exception cref="NotSupportedException">Always thrown</exception>
 243         public override void SetLength(long value)
 244         {
 245             throw new NotSupportedException();
 246         }
 247 
 248         /// <summary>
 249         ///  Not suppported.
 250         /// </summary>
 251         /// <param name="offset"></param>
 252         /// <param name="origin"></param>
 253         /// <returns></returns>
 254         /// <exception cref="NotSupportedException">Always thrown</exception>
 255         public override long Seek(long offset, SeekOrigin origin)
 256         {
 257             throw new NotSupportedException();
 258         }
 259 
 260         /// <summary>
 261         /// Flushes the <c>GZipStream</c>.
 262         /// </summary>
 263         /// <remarks>In this implementation, this method does nothing. This is because excessive
 264         /// flushing may degrade the achievable compression rates.</remarks>
 265         public override void Flush()
 266         {
 267             // left empty on purpose
 268         }
 269 
 270         /// <summary>
 271         /// Gets/sets the current position in the <c>GZipStream</c>. Not suppported.
 272         /// </summary>
 273         /// <remarks>In this implementation this property is not supported</remarks>
 274         /// <exception cref="NotSupportedException">Always thrown</exception>
 275         public override long Position
 276         {
 277             get
 278             {
 279                 throw new NotSupportedException();
 280             }
 281             set
 282             {
 283                 throw new NotSupportedException();
 284             }
 285         }
 286 
 287         /// <summary>
 288         /// Gets the size of the stream. Not suppported.
 289         /// </summary>
 290         /// <remarks>In this implementation this property is not supported</remarks>
 291         /// <exception cref="NotSupportedException">Always thrown</exception>
 292         public override long Length
 293         {
 294             get
 295             {
 296                 throw new NotSupportedException();
 297             }
 298         }
 299         #endregion
 300     }
 301 }