1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
|
// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
namespace WixToolset.Dtf.Compression
{
using System;
using System.IO;
using System.Collections.Generic;
using System.Globalization;
/// <summary>
/// Base class for an engine capable of packing and unpacking a particular
/// compressed file format.
/// </summary>
public abstract class CompressionEngine : IDisposable
{
private CompressionLevel compressionLevel;
private bool dontUseTempFiles;
/// <summary>
/// Creates a new instance of the compression engine base class.
/// </summary>
protected CompressionEngine()
{
this.compressionLevel = CompressionLevel.Normal;
}
/// <summary>
/// Disposes the compression engine.
/// </summary>
~CompressionEngine()
{
this.Dispose(false);
}
/// <summary>
/// Occurs when the compression engine reports progress in packing
/// or unpacking an archive.
/// </summary>
/// <seealso cref="ArchiveProgressType"/>
public event EventHandler<ArchiveProgressEventArgs> Progress;
/// <summary>
/// Gets or sets a flag indicating whether temporary files are created
/// and used during compression.
/// </summary>
/// <value>True if temporary files are used; false if compression is done
/// entirely in-memory.</value>
/// <remarks>The value of this property is true by default. Using temporary
/// files can greatly reduce the memory requirement of compression,
/// especially when compressing large archives. However, setting this property
/// to false may yield slightly better performance when creating small
/// archives. Or it may be necessary if the process does not have sufficient
/// privileges to create temporary files.</remarks>
public bool UseTempFiles
{
get
{
return !this.dontUseTempFiles;
}
set
{
this.dontUseTempFiles = !value;
}
}
/// <summary>
/// Compression level to use when compressing files.
/// </summary>
/// <value>A compression level ranging from minimum to maximum compression,
/// or no compression.</value>
public CompressionLevel CompressionLevel
{
get
{
return this.compressionLevel;
}
set
{
this.compressionLevel = value;
}
}
/// <summary>
/// Disposes of resources allocated by the compression engine.
/// </summary>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Creates an archive.
/// </summary>
/// <param name="streamContext">A context interface to handle opening
/// and closing of archive and file streams.</param>
/// <param name="files">The paths of the files in the archive
/// (not external file paths).</param>
/// <exception cref="ArchiveException">The archive could not be
/// created.</exception>
/// <remarks>
/// The stream context implementation may provide a mapping from the
/// file paths within the archive to the external file paths.
/// </remarks>
public void Pack(IPackStreamContext streamContext, IEnumerable<string> files)
{
if (files == null)
{
throw new ArgumentNullException("files");
}
this.Pack(streamContext, files, 0);
}
/// <summary>
/// Creates an archive or chain of archives.
/// </summary>
/// <param name="streamContext">A context interface to handle opening
/// and closing of archive and file streams.</param>
/// <param name="files">The paths of the files in the archive (not
/// external file paths).</param>
/// <param name="maxArchiveSize">The maximum number of bytes for one
/// archive before the contents are chained to the next archive, or zero
/// for unlimited archive size.</param>
/// <exception cref="ArchiveException">The archive could not be
/// created.</exception>
/// <remarks>
/// The stream context implementation may provide a mapping from the file
/// paths within the archive to the external file paths.
/// </remarks>
public abstract void Pack(
IPackStreamContext streamContext,
IEnumerable<string> files,
long maxArchiveSize);
/// <summary>
/// Checks whether a Stream begins with a header that indicates
/// it is a valid archive.
/// </summary>
/// <param name="stream">Stream for reading the archive file.</param>
/// <returns>True if the stream is a valid archive
/// (with no offset); false otherwise.</returns>
public abstract bool IsArchive(Stream stream);
/// <summary>
/// Gets the offset of an archive that is positioned 0 or more bytes
/// from the start of the Stream.
/// </summary>
/// <param name="stream">A stream for reading the archive.</param>
/// <returns>The offset in bytes of the archive,
/// or -1 if no archive is found in the Stream.</returns>
/// <remarks>The archive must begin on a 4-byte boundary.</remarks>
public virtual long FindArchiveOffset(Stream stream)
{
if (stream == null)
{
throw new ArgumentNullException("stream");
}
long sectionSize = 4;
long length = stream.Length;
for (long offset = 0; offset <= length - sectionSize; offset += sectionSize)
{
stream.Seek(offset, SeekOrigin.Begin);
if (this.IsArchive(stream))
{
return offset;
}
}
return -1;
}
/// <summary>
/// Gets information about all files in an archive stream.
/// </summary>
/// <param name="stream">A stream for reading the archive.</param>
/// <returns>Information about all files in the archive stream.</returns>
/// <exception cref="ArchiveException">The stream is not a valid
/// archive.</exception>
public IList<ArchiveFileInfo> GetFileInfo(Stream stream)
{
return this.GetFileInfo(new BasicUnpackStreamContext(stream), null);
}
/// <summary>
/// Gets information about files in an archive or archive chain.
/// </summary>
/// <param name="streamContext">A context interface to handle opening
/// and closing of archive and file streams.</param>
/// <param name="fileFilter">A predicate that can determine
/// which files to process, optional.</param>
/// <returns>Information about files in the archive stream.</returns>
/// <exception cref="ArchiveException">The archive provided
/// by the stream context is not valid.</exception>
/// <remarks>
/// The <paramref name="fileFilter"/> predicate takes an internal file
/// path and returns true to include the file or false to exclude it.
/// </remarks>
public abstract IList<ArchiveFileInfo> GetFileInfo(
IUnpackStreamContext streamContext,
Predicate<string> fileFilter);
/// <summary>
/// Gets the list of files in an archive Stream.
/// </summary>
/// <param name="stream">A stream for reading the archive.</param>
/// <returns>A list of the paths of all files contained in the
/// archive.</returns>
/// <exception cref="ArchiveException">The stream is not a valid
/// archive.</exception>
public IList<string> GetFiles(Stream stream)
{
return this.GetFiles(new BasicUnpackStreamContext(stream), null);
}
/// <summary>
/// Gets the list of files in an archive or archive chain.
/// </summary>
/// <param name="streamContext">A context interface to handle opening
/// and closing of archive and file streams.</param>
/// <param name="fileFilter">A predicate that can determine
/// which files to process, optional.</param>
/// <returns>An array containing the names of all files contained in
/// the archive or archive chain.</returns>
/// <exception cref="ArchiveException">The archive provided
/// by the stream context is not valid.</exception>
/// <remarks>
/// The <paramref name="fileFilter"/> predicate takes an internal file
/// path and returns true to include the file or false to exclude it.
/// </remarks>
public IList<string> GetFiles(
IUnpackStreamContext streamContext,
Predicate<string> fileFilter)
{
if (streamContext == null)
{
throw new ArgumentNullException("streamContext");
}
IList<ArchiveFileInfo> files =
this.GetFileInfo(streamContext, fileFilter);
IList<string> fileNames = new List<string>(files.Count);
for (int i = 0; i < files.Count; i++)
{
fileNames.Add(files[i].Name);
}
return fileNames;
}
/// <summary>
/// Reads a single file from an archive stream.
/// </summary>
/// <param name="stream">A stream for reading the archive.</param>
/// <param name="path">The path of the file within the archive
/// (not the external file path).</param>
/// <returns>A stream for reading the extracted file, or null
/// if the file does not exist in the archive.</returns>
/// <exception cref="ArchiveException">The stream is not a valid
/// archive.</exception>
/// <remarks>The entire extracted file is cached in memory, so this
/// method requires enough free memory to hold the file.</remarks>
public Stream Unpack(Stream stream, string path)
{
if (stream == null)
{
throw new ArgumentNullException("stream");
}
if (path == null)
{
throw new ArgumentNullException("path");
}
BasicUnpackStreamContext streamContext =
new BasicUnpackStreamContext(stream);
this.Unpack(
streamContext,
delegate(string match)
{
return String.Compare(
match, path, true, CultureInfo.InvariantCulture) == 0;
});
Stream extractStream = streamContext.FileStream;
if (extractStream != null)
{
extractStream.Position = 0;
}
return extractStream;
}
/// <summary>
/// Extracts files from an archive or archive chain.
/// </summary>
/// <param name="streamContext">A context interface to handle opening
/// and closing of archive and file streams.</param>
/// <param name="fileFilter">An optional predicate that can determine
/// which files to process.</param>
/// <exception cref="ArchiveException">The archive provided
/// by the stream context is not valid.</exception>
/// <remarks>
/// The <paramref name="fileFilter"/> predicate takes an internal file
/// path and returns true to include the file or false to exclude it.
/// </remarks>
public abstract void Unpack(
IUnpackStreamContext streamContext,
Predicate<string> fileFilter);
/// <summary>
/// Called by sublcasses to distribute a packing or unpacking progress
/// event to listeners.
/// </summary>
/// <param name="e">Event details.</param>
protected void OnProgress(ArchiveProgressEventArgs e)
{
if (this.Progress != null)
{
this.Progress(this, e);
}
}
/// <summary>
/// Disposes of resources allocated by the compression engine.
/// </summary>
/// <param name="disposing">If true, the method has been called
/// directly or indirectly by a user's code, so managed and unmanaged
/// resources will be disposed. If false, the method has been called by
/// the runtime from inside the finalizer, and only unmanaged resources
/// will be disposed.</param>
protected virtual void Dispose(bool disposing)
{
}
/// <summary>
/// Compresion utility function for converting old-style
/// date and time values to a DateTime structure.
/// </summary>
public static void DosDateAndTimeToDateTime(
short dosDate, short dosTime, out DateTime dateTime)
{
if (dosDate == 0 && dosTime == 0)
{
dateTime = DateTime.MinValue;
}
else
{
long fileTime;
SafeNativeMethods.DosDateTimeToFileTime(dosDate, dosTime, out fileTime);
dateTime = DateTime.FromFileTimeUtc(fileTime);
dateTime = new DateTime(dateTime.Ticks, DateTimeKind.Local);
}
}
/// <summary>
/// Compresion utility function for converting a DateTime structure
/// to old-style date and time values.
/// </summary>
public static void DateTimeToDosDateAndTime(
DateTime dateTime, out short dosDate, out short dosTime)
{
dateTime = new DateTime(dateTime.Ticks, DateTimeKind.Utc);
long filetime = dateTime.ToFileTimeUtc();
SafeNativeMethods.FileTimeToDosDateTime(ref filetime, out dosDate, out dosTime);
}
}
}
|