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