diff options
author | Rob Mensching <rob@firegiant.com> | 2021-05-11 07:36:37 -0700 |
---|---|---|
committer | Rob Mensching <rob@firegiant.com> | 2021-05-11 07:36:37 -0700 |
commit | 3f583916719eeef598d10a5d4e14ef14f008243b (patch) | |
tree | 3d528e0ddb5c0550954217c97059d2f19cd6152a /src/dtf/WixToolset.Dtf.Compression/CompressionEngine.cs | |
parent | 2e5ab696b8b4666d551b2a0532b95fb7fe6dbe03 (diff) | |
download | wix-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.cs | 371 |
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 | |||
3 | namespace WixToolset.Dtf.Compression | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Collections.Generic; | ||
8 | using 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 | } | ||