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