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 > 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 > 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 }