diff options
Diffstat (limited to '')
-rw-r--r-- | src/dtf/WixToolset.Dtf.Compression/ArchiveInfo.cs | 781 |
1 files changed, 781 insertions, 0 deletions
diff --git a/src/dtf/WixToolset.Dtf.Compression/ArchiveInfo.cs b/src/dtf/WixToolset.Dtf.Compression/ArchiveInfo.cs new file mode 100644 index 00000000..b5da4ea8 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.Compression/ArchiveInfo.cs | |||
@@ -0,0 +1,781 @@ | |||
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 | using System.Text; | ||
10 | using System.Text.RegularExpressions; | ||
11 | using System.Runtime.Serialization; | ||
12 | using System.Diagnostics.CodeAnalysis; | ||
13 | |||
14 | /// <summary> | ||
15 | /// Abstract object representing a compressed archive on disk; | ||
16 | /// provides access to file-based operations on the archive. | ||
17 | /// </summary> | ||
18 | [Serializable] | ||
19 | public abstract class ArchiveInfo : FileSystemInfo | ||
20 | { | ||
21 | /// <summary> | ||
22 | /// Creates a new ArchiveInfo object representing an archive in a | ||
23 | /// specified path. | ||
24 | /// </summary> | ||
25 | /// <param name="path">The path to the archive. When creating an archive, | ||
26 | /// this file does not necessarily exist yet.</param> | ||
27 | protected ArchiveInfo(string path) : base() | ||
28 | { | ||
29 | if (path == null) | ||
30 | { | ||
31 | throw new ArgumentNullException("path"); | ||
32 | } | ||
33 | |||
34 | // protected instance members inherited from FileSystemInfo: | ||
35 | this.OriginalPath = path; | ||
36 | this.FullPath = Path.GetFullPath(path); | ||
37 | } | ||
38 | |||
39 | /// <summary> | ||
40 | /// Initializes a new instance of the ArchiveInfo class with serialized data. | ||
41 | /// </summary> | ||
42 | /// <param name="info">The SerializationInfo that holds the serialized object | ||
43 | /// data about the exception being thrown.</param> | ||
44 | /// <param name="context">The StreamingContext that contains contextual | ||
45 | /// information about the source or destination.</param> | ||
46 | protected ArchiveInfo(SerializationInfo info, StreamingContext context) | ||
47 | : base(info, context) | ||
48 | { | ||
49 | } | ||
50 | |||
51 | /// <summary> | ||
52 | /// Gets the directory that contains the archive. | ||
53 | /// </summary> | ||
54 | /// <value>A DirectoryInfo object representing the parent directory of the | ||
55 | /// archive.</value> | ||
56 | public DirectoryInfo Directory | ||
57 | { | ||
58 | get | ||
59 | { | ||
60 | return new DirectoryInfo(Path.GetDirectoryName(this.FullName)); | ||
61 | } | ||
62 | } | ||
63 | |||
64 | /// <summary> | ||
65 | /// Gets the full path of the directory that contains the archive. | ||
66 | /// </summary> | ||
67 | /// <value>The full path of the directory that contains the archive.</value> | ||
68 | public string DirectoryName | ||
69 | { | ||
70 | get | ||
71 | { | ||
72 | return Path.GetDirectoryName(this.FullName); | ||
73 | } | ||
74 | } | ||
75 | |||
76 | /// <summary> | ||
77 | /// Gets the size of the archive. | ||
78 | /// </summary> | ||
79 | /// <value>The size of the archive in bytes.</value> | ||
80 | public long Length | ||
81 | { | ||
82 | get | ||
83 | { | ||
84 | return new FileInfo(this.FullName).Length; | ||
85 | } | ||
86 | } | ||
87 | |||
88 | /// <summary> | ||
89 | /// Gets the file name of the archive. | ||
90 | /// </summary> | ||
91 | /// <value>The file name of the archive, not including any path.</value> | ||
92 | public override string Name | ||
93 | { | ||
94 | get | ||
95 | { | ||
96 | return Path.GetFileName(this.FullName); | ||
97 | } | ||
98 | } | ||
99 | |||
100 | /// <summary> | ||
101 | /// Checks if the archive exists. | ||
102 | /// </summary> | ||
103 | /// <value>True if the archive exists; else false.</value> | ||
104 | public override bool Exists | ||
105 | { | ||
106 | get | ||
107 | { | ||
108 | return File.Exists(this.FullName); | ||
109 | } | ||
110 | } | ||
111 | |||
112 | /// <summary> | ||
113 | /// Gets the full path of the archive. | ||
114 | /// </summary> | ||
115 | /// <returns>The full path of the archive.</returns> | ||
116 | public override string ToString() | ||
117 | { | ||
118 | return this.FullName; | ||
119 | } | ||
120 | |||
121 | /// <summary> | ||
122 | /// Deletes the archive. | ||
123 | /// </summary> | ||
124 | public override void Delete() | ||
125 | { | ||
126 | File.Delete(this.FullName); | ||
127 | } | ||
128 | |||
129 | /// <summary> | ||
130 | /// Copies an existing archive to another location. | ||
131 | /// </summary> | ||
132 | /// <param name="destFileName">The destination file path.</param> | ||
133 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
134 | public void CopyTo(string destFileName) | ||
135 | { | ||
136 | File.Copy(this.FullName, destFileName); | ||
137 | } | ||
138 | |||
139 | /// <summary> | ||
140 | /// Copies an existing archive to another location, optionally | ||
141 | /// overwriting the destination file. | ||
142 | /// </summary> | ||
143 | /// <param name="destFileName">The destination file path.</param> | ||
144 | /// <param name="overwrite">If true, the destination file will be | ||
145 | /// overwritten if it exists.</param> | ||
146 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
147 | public void CopyTo(string destFileName, bool overwrite) | ||
148 | { | ||
149 | File.Copy(this.FullName, destFileName, overwrite); | ||
150 | } | ||
151 | |||
152 | /// <summary> | ||
153 | /// Moves an existing archive to another location. | ||
154 | /// </summary> | ||
155 | /// <param name="destFileName">The destination file path.</param> | ||
156 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
157 | public void MoveTo(string destFileName) | ||
158 | { | ||
159 | File.Move(this.FullName, destFileName); | ||
160 | this.FullPath = Path.GetFullPath(destFileName); | ||
161 | } | ||
162 | |||
163 | /// <summary> | ||
164 | /// Checks if the archive contains a valid archive header. | ||
165 | /// </summary> | ||
166 | /// <returns>True if the file is a valid archive; false otherwise.</returns> | ||
167 | public bool IsValid() | ||
168 | { | ||
169 | using (Stream stream = File.OpenRead(this.FullName)) | ||
170 | { | ||
171 | using (CompressionEngine compressionEngine = this.CreateCompressionEngine()) | ||
172 | { | ||
173 | return compressionEngine.FindArchiveOffset(stream) >= 0; | ||
174 | } | ||
175 | } | ||
176 | } | ||
177 | |||
178 | /// <summary> | ||
179 | /// Gets information about the files contained in the archive. | ||
180 | /// </summary> | ||
181 | /// <returns>A list of <see cref="ArchiveFileInfo"/> objects, each | ||
182 | /// containing information about a file in the archive.</returns> | ||
183 | public IList<ArchiveFileInfo> GetFiles() | ||
184 | { | ||
185 | return this.InternalGetFiles((Predicate<string>) null); | ||
186 | } | ||
187 | |||
188 | /// <summary> | ||
189 | /// Gets information about the certain files contained in the archive file. | ||
190 | /// </summary> | ||
191 | /// <param name="searchPattern">The search string, such as | ||
192 | /// "*.txt".</param> | ||
193 | /// <returns>A list of <see cref="ArchiveFileInfo"/> objects, each containing | ||
194 | /// information about a file in the archive.</returns> | ||
195 | public IList<ArchiveFileInfo> GetFiles(string searchPattern) | ||
196 | { | ||
197 | if (searchPattern == null) | ||
198 | { | ||
199 | throw new ArgumentNullException("searchPattern"); | ||
200 | } | ||
201 | |||
202 | string regexPattern = String.Format( | ||
203 | CultureInfo.InvariantCulture, | ||
204 | "^{0}$", | ||
205 | Regex.Escape(searchPattern).Replace("\\*", ".*").Replace("\\?", ".")); | ||
206 | Regex regex = new Regex( | ||
207 | regexPattern, | ||
208 | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); | ||
209 | |||
210 | return this.InternalGetFiles( | ||
211 | delegate(string match) | ||
212 | { | ||
213 | return regex.IsMatch(match); | ||
214 | }); | ||
215 | } | ||
216 | |||
217 | /// <summary> | ||
218 | /// Extracts all files from an archive to a destination directory. | ||
219 | /// </summary> | ||
220 | /// <param name="destDirectory">Directory where the files are to be | ||
221 | /// extracted.</param> | ||
222 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
223 | public void Unpack(string destDirectory) | ||
224 | { | ||
225 | this.Unpack(destDirectory, null); | ||
226 | } | ||
227 | |||
228 | /// <summary> | ||
229 | /// Extracts all files from an archive to a destination directory, | ||
230 | /// optionally extracting only newer files. | ||
231 | /// </summary> | ||
232 | /// <param name="destDirectory">Directory where the files are to be | ||
233 | /// extracted.</param> | ||
234 | /// <param name="progressHandler">Handler for receiving progress | ||
235 | /// information; this may be null if progress is not desired.</param> | ||
236 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
237 | public void Unpack( | ||
238 | string destDirectory, | ||
239 | EventHandler<ArchiveProgressEventArgs> progressHandler) | ||
240 | { | ||
241 | using (CompressionEngine compressionEngine = this.CreateCompressionEngine()) | ||
242 | { | ||
243 | compressionEngine.Progress += progressHandler; | ||
244 | ArchiveFileStreamContext streamContext = | ||
245 | new ArchiveFileStreamContext(this.FullName, destDirectory, null); | ||
246 | streamContext.EnableOffsetOpen = true; | ||
247 | compressionEngine.Unpack(streamContext, null); | ||
248 | } | ||
249 | } | ||
250 | |||
251 | /// <summary> | ||
252 | /// Extracts a single file from the archive. | ||
253 | /// </summary> | ||
254 | /// <param name="fileName">The name of the file in the archive. Also | ||
255 | /// includes the internal path of the file, if any. File name matching | ||
256 | /// is case-insensitive.</param> | ||
257 | /// <param name="destFileName">The path where the file is to be | ||
258 | /// extracted on disk.</param> | ||
259 | /// <remarks>If <paramref name="destFileName"/> already exists, | ||
260 | /// it will be overwritten.</remarks> | ||
261 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
262 | public void UnpackFile(string fileName, string destFileName) | ||
263 | { | ||
264 | if (fileName == null) | ||
265 | { | ||
266 | throw new ArgumentNullException("fileName"); | ||
267 | } | ||
268 | |||
269 | if (destFileName == null) | ||
270 | { | ||
271 | throw new ArgumentNullException("destFileName"); | ||
272 | } | ||
273 | |||
274 | this.UnpackFiles( | ||
275 | new string[] { fileName }, | ||
276 | null, | ||
277 | new string[] { destFileName }); | ||
278 | } | ||
279 | |||
280 | /// <summary> | ||
281 | /// Extracts multiple files from the archive. | ||
282 | /// </summary> | ||
283 | /// <param name="fileNames">The names of the files in the archive. | ||
284 | /// Each name includes the internal path of the file, if any. File name | ||
285 | /// matching is case-insensitive.</param> | ||
286 | /// <param name="destDirectory">This parameter may be null, but if | ||
287 | /// specified it is the root directory for any relative paths in | ||
288 | /// <paramref name="destFileNames"/>.</param> | ||
289 | /// <param name="destFileNames">The paths where the files are to be | ||
290 | /// extracted on disk. If this parameter is null, the files will be | ||
291 | /// extracted with the names from the archive.</param> | ||
292 | /// <remarks> | ||
293 | /// If any extracted files already exist on disk, they will be overwritten. | ||
294 | /// <p>The <paramref name="destDirectory"/> and | ||
295 | /// <paramref name="destFileNames"/> parameters cannot both be null.</p> | ||
296 | /// </remarks> | ||
297 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
298 | public void UnpackFiles( | ||
299 | IList<string> fileNames, | ||
300 | string destDirectory, | ||
301 | IList<string> destFileNames) | ||
302 | { | ||
303 | this.UnpackFiles(fileNames, destDirectory, destFileNames, null); | ||
304 | } | ||
305 | |||
306 | /// <summary> | ||
307 | /// Extracts multiple files from the archive, optionally extracting | ||
308 | /// only newer files. | ||
309 | /// </summary> | ||
310 | /// <param name="fileNames">The names of the files in the archive. | ||
311 | /// Each name includes the internal path of the file, if any. File name | ||
312 | /// matching is case-insensitive.</param> | ||
313 | /// <param name="destDirectory">This parameter may be null, but if | ||
314 | /// specified it is the root directory for any relative paths in | ||
315 | /// <paramref name="destFileNames"/>.</param> | ||
316 | /// <param name="destFileNames">The paths where the files are to be | ||
317 | /// extracted on disk. If this parameter is null, the files will be | ||
318 | /// extracted with the names from the archive.</param> | ||
319 | /// <param name="progressHandler">Handler for receiving progress information; | ||
320 | /// this may be null if progress is not desired.</param> | ||
321 | /// <remarks> | ||
322 | /// If any extracted files already exist on disk, they will be overwritten. | ||
323 | /// <p>The <paramref name="destDirectory"/> and | ||
324 | /// <paramref name="destFileNames"/> parameters cannot both be null.</p> | ||
325 | /// </remarks> | ||
326 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
327 | public void UnpackFiles( | ||
328 | IList<string> fileNames, | ||
329 | string destDirectory, | ||
330 | IList<string> destFileNames, | ||
331 | EventHandler<ArchiveProgressEventArgs> progressHandler) | ||
332 | { | ||
333 | if (fileNames == null) | ||
334 | { | ||
335 | throw new ArgumentNullException("fileNames"); | ||
336 | } | ||
337 | |||
338 | if (destFileNames == null) | ||
339 | { | ||
340 | if (destDirectory == null) | ||
341 | { | ||
342 | throw new ArgumentNullException("destFileNames"); | ||
343 | } | ||
344 | |||
345 | destFileNames = fileNames; | ||
346 | } | ||
347 | |||
348 | if (destFileNames.Count != fileNames.Count) | ||
349 | { | ||
350 | throw new ArgumentOutOfRangeException("destFileNames"); | ||
351 | } | ||
352 | |||
353 | IDictionary<string, string> files = | ||
354 | ArchiveInfo.CreateStringDictionary(fileNames, destFileNames); | ||
355 | this.UnpackFileSet(files, destDirectory, progressHandler); | ||
356 | } | ||
357 | |||
358 | /// <summary> | ||
359 | /// Extracts multiple files from the archive. | ||
360 | /// </summary> | ||
361 | /// <param name="fileNames">A mapping from internal file paths to | ||
362 | /// external file paths. Case-senstivity when matching internal paths | ||
363 | /// depends on the IDictionary implementation.</param> | ||
364 | /// <param name="destDirectory">This parameter may be null, but if | ||
365 | /// specified it is the root directory for any relative external paths | ||
366 | /// in <paramref name="fileNames"/>.</param> | ||
367 | /// <remarks> | ||
368 | /// If any extracted files already exist on disk, they will be overwritten. | ||
369 | /// </remarks> | ||
370 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
371 | public void UnpackFileSet( | ||
372 | IDictionary<string, string> fileNames, | ||
373 | string destDirectory) | ||
374 | { | ||
375 | this.UnpackFileSet(fileNames, destDirectory, null); | ||
376 | } | ||
377 | |||
378 | /// <summary> | ||
379 | /// Extracts multiple files from the archive. | ||
380 | /// </summary> | ||
381 | /// <param name="fileNames">A mapping from internal file paths to | ||
382 | /// external file paths. Case-senstivity when matching internal | ||
383 | /// paths depends on the IDictionary implementation.</param> | ||
384 | /// <param name="destDirectory">This parameter may be null, but if | ||
385 | /// specified it is the root directory for any relative external | ||
386 | /// paths in <paramref name="fileNames"/>.</param> | ||
387 | /// <param name="progressHandler">Handler for receiving progress | ||
388 | /// information; this may be null if progress is not desired.</param> | ||
389 | /// <remarks> | ||
390 | /// If any extracted files already exist on disk, they will be overwritten. | ||
391 | /// </remarks> | ||
392 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
393 | public void UnpackFileSet( | ||
394 | IDictionary<string, string> fileNames, | ||
395 | string destDirectory, | ||
396 | EventHandler<ArchiveProgressEventArgs> progressHandler) | ||
397 | { | ||
398 | if (fileNames == null) | ||
399 | { | ||
400 | throw new ArgumentNullException("fileNames"); | ||
401 | } | ||
402 | |||
403 | using (CompressionEngine compressionEngine = this.CreateCompressionEngine()) | ||
404 | { | ||
405 | compressionEngine.Progress += progressHandler; | ||
406 | ArchiveFileStreamContext streamContext = | ||
407 | new ArchiveFileStreamContext(this.FullName, destDirectory, fileNames); | ||
408 | streamContext.EnableOffsetOpen = true; | ||
409 | compressionEngine.Unpack( | ||
410 | streamContext, | ||
411 | delegate(string match) | ||
412 | { | ||
413 | return fileNames.ContainsKey(match); | ||
414 | }); | ||
415 | } | ||
416 | } | ||
417 | |||
418 | /// <summary> | ||
419 | /// Opens a file inside the archive for reading without actually | ||
420 | /// extracting the file to disk. | ||
421 | /// </summary> | ||
422 | /// <param name="fileName">The name of the file in the archive. Also | ||
423 | /// includes the internal path of the file, if any. File name matching | ||
424 | /// is case-insensitive.</param> | ||
425 | /// <returns> | ||
426 | /// A stream for reading directly from the packed file. Like any stream | ||
427 | /// this should be closed/disposed as soon as it is no longer needed. | ||
428 | /// </returns> | ||
429 | public Stream OpenRead(string fileName) | ||
430 | { | ||
431 | Stream archiveStream = File.OpenRead(this.FullName); | ||
432 | CompressionEngine compressionEngine = this.CreateCompressionEngine(); | ||
433 | Stream fileStream = compressionEngine.Unpack(archiveStream, fileName); | ||
434 | |||
435 | // Attach the archiveStream and compressionEngine to the | ||
436 | // fileStream so they get disposed when the fileStream is disposed. | ||
437 | return new CargoStream(fileStream, archiveStream, compressionEngine); | ||
438 | } | ||
439 | |||
440 | /// <summary> | ||
441 | /// Opens a file inside the archive for reading text with UTF-8 encoding | ||
442 | /// without actually extracting the file to disk. | ||
443 | /// </summary> | ||
444 | /// <param name="fileName">The name of the file in the archive. Also | ||
445 | /// includes the internal path of the file, if any. File name matching | ||
446 | /// is case-insensitive.</param> | ||
447 | /// <returns> | ||
448 | /// A reader for reading text directly from the packed file. Like any reader | ||
449 | /// this should be closed/disposed as soon as it is no longer needed. | ||
450 | /// </returns> | ||
451 | /// <remarks> | ||
452 | /// To open an archived text file with different encoding, use the | ||
453 | /// <see cref="OpenRead" /> method and pass the returned stream to one of | ||
454 | /// the <see cref="StreamReader" /> constructor overloads. | ||
455 | /// </remarks> | ||
456 | public StreamReader OpenText(string fileName) | ||
457 | { | ||
458 | return new StreamReader(this.OpenRead(fileName)); | ||
459 | } | ||
460 | |||
461 | /// <summary> | ||
462 | /// Compresses all files in a directory into the archive. | ||
463 | /// Does not include subdirectories. | ||
464 | /// </summary> | ||
465 | /// <param name="sourceDirectory">The directory containing the | ||
466 | /// files to be included.</param> | ||
467 | /// <remarks> | ||
468 | /// Uses maximum compression level. | ||
469 | /// </remarks> | ||
470 | public void Pack(string sourceDirectory) | ||
471 | { | ||
472 | this.Pack(sourceDirectory, false, CompressionLevel.Max, null); | ||
473 | } | ||
474 | |||
475 | /// <summary> | ||
476 | /// Compresses all files in a directory into the archive, optionally | ||
477 | /// including subdirectories. | ||
478 | /// </summary> | ||
479 | /// <param name="sourceDirectory">This is the root directory | ||
480 | /// for to pack all files.</param> | ||
481 | /// <param name="includeSubdirectories">If true, recursively include | ||
482 | /// files in subdirectories.</param> | ||
483 | /// <param name="compLevel">The compression level used when creating | ||
484 | /// the archive.</param> | ||
485 | /// <param name="progressHandler">Handler for receiving progress information; | ||
486 | /// this may be null if progress is not desired.</param> | ||
487 | /// <remarks> | ||
488 | /// The files are stored in the archive using their relative file paths in | ||
489 | /// the directory tree, if supported by the archive file format. | ||
490 | /// </remarks> | ||
491 | public void Pack( | ||
492 | string sourceDirectory, | ||
493 | bool includeSubdirectories, | ||
494 | CompressionLevel compLevel, | ||
495 | EventHandler<ArchiveProgressEventArgs> progressHandler) | ||
496 | { | ||
497 | IList<string> files = this.GetRelativeFilePathsInDirectoryTree( | ||
498 | sourceDirectory, includeSubdirectories); | ||
499 | this.PackFiles(sourceDirectory, files, files, compLevel, progressHandler); | ||
500 | } | ||
501 | |||
502 | /// <summary> | ||
503 | /// Compresses files into the archive, specifying the names used to | ||
504 | /// store the files in the archive. | ||
505 | /// </summary> | ||
506 | /// <param name="sourceDirectory">This parameter may be null, but | ||
507 | /// if specified it is the root directory | ||
508 | /// for any relative paths in <paramref name="sourceFileNames"/>.</param> | ||
509 | /// <param name="sourceFileNames">The list of files to be included in | ||
510 | /// the archive.</param> | ||
511 | /// <param name="fileNames">The names of the files as they are stored | ||
512 | /// in the archive. Each name | ||
513 | /// includes the internal path of the file, if any. This parameter may | ||
514 | /// be null, in which case the files are stored in the archive with their | ||
515 | /// source file names and no path information.</param> | ||
516 | /// <remarks> | ||
517 | /// Uses maximum compression level. | ||
518 | /// <p>Duplicate items in the <paramref name="fileNames"/> array will cause | ||
519 | /// an <see cref="ArchiveException"/>.</p> | ||
520 | /// </remarks> | ||
521 | public void PackFiles( | ||
522 | string sourceDirectory, | ||
523 | IList<string> sourceFileNames, | ||
524 | IList<string> fileNames) | ||
525 | { | ||
526 | this.PackFiles( | ||
527 | sourceDirectory, | ||
528 | sourceFileNames, | ||
529 | fileNames, | ||
530 | CompressionLevel.Max, | ||
531 | null); | ||
532 | } | ||
533 | |||
534 | /// <summary> | ||
535 | /// Compresses files into the archive, specifying the names used to | ||
536 | /// store the files in the archive. | ||
537 | /// </summary> | ||
538 | /// <param name="sourceDirectory">This parameter may be null, but if | ||
539 | /// specified it is the root directory | ||
540 | /// for any relative paths in <paramref name="sourceFileNames"/>.</param> | ||
541 | /// <param name="sourceFileNames">The list of files to be included in | ||
542 | /// the archive.</param> | ||
543 | /// <param name="fileNames">The names of the files as they are stored in | ||
544 | /// the archive. Each name includes the internal path of the file, if any. | ||
545 | /// This parameter may be null, in which case the files are stored in the | ||
546 | /// archive with their source file names and no path information.</param> | ||
547 | /// <param name="compLevel">The compression level used when creating the | ||
548 | /// archive.</param> | ||
549 | /// <param name="progressHandler">Handler for receiving progress information; | ||
550 | /// this may be null if progress is not desired.</param> | ||
551 | /// <remarks> | ||
552 | /// Duplicate items in the <paramref name="fileNames"/> array will cause | ||
553 | /// an <see cref="ArchiveException"/>. | ||
554 | /// </remarks> | ||
555 | public void PackFiles( | ||
556 | string sourceDirectory, | ||
557 | IList<string> sourceFileNames, | ||
558 | IList<string> fileNames, | ||
559 | CompressionLevel compLevel, | ||
560 | EventHandler<ArchiveProgressEventArgs> progressHandler) | ||
561 | { | ||
562 | if (sourceFileNames == null) | ||
563 | { | ||
564 | throw new ArgumentNullException("sourceFileNames"); | ||
565 | } | ||
566 | |||
567 | if (fileNames == null) | ||
568 | { | ||
569 | string[] fileNamesArray = new string[sourceFileNames.Count]; | ||
570 | for (int i = 0; i < sourceFileNames.Count; i++) | ||
571 | { | ||
572 | fileNamesArray[i] = Path.GetFileName(sourceFileNames[i]); | ||
573 | } | ||
574 | |||
575 | fileNames = fileNamesArray; | ||
576 | } | ||
577 | else if (fileNames.Count != sourceFileNames.Count) | ||
578 | { | ||
579 | throw new ArgumentOutOfRangeException("fileNames"); | ||
580 | } | ||
581 | |||
582 | using (CompressionEngine compressionEngine = this.CreateCompressionEngine()) | ||
583 | { | ||
584 | compressionEngine.Progress += progressHandler; | ||
585 | IDictionary<string, string> contextFiles = | ||
586 | ArchiveInfo.CreateStringDictionary(fileNames, sourceFileNames); | ||
587 | ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext( | ||
588 | this.FullName, sourceDirectory, contextFiles); | ||
589 | streamContext.EnableOffsetOpen = true; | ||
590 | compressionEngine.CompressionLevel = compLevel; | ||
591 | compressionEngine.Pack(streamContext, fileNames); | ||
592 | } | ||
593 | } | ||
594 | |||
595 | /// <summary> | ||
596 | /// Compresses files into the archive, specifying the names used | ||
597 | /// to store the files in the archive. | ||
598 | /// </summary> | ||
599 | /// <param name="sourceDirectory">This parameter may be null, but if | ||
600 | /// specified it is the root directory | ||
601 | /// for any relative paths in <paramref name="fileNames"/>.</param> | ||
602 | /// <param name="fileNames">A mapping from internal file paths to | ||
603 | /// external file paths.</param> | ||
604 | /// <remarks> | ||
605 | /// Uses maximum compression level. | ||
606 | /// </remarks> | ||
607 | public void PackFileSet( | ||
608 | string sourceDirectory, | ||
609 | IDictionary<string, string> fileNames) | ||
610 | { | ||
611 | this.PackFileSet(sourceDirectory, fileNames, CompressionLevel.Max, null); | ||
612 | } | ||
613 | |||
614 | /// <summary> | ||
615 | /// Compresses files into the archive, specifying the names used to | ||
616 | /// store the files in the archive. | ||
617 | /// </summary> | ||
618 | /// <param name="sourceDirectory">This parameter may be null, but if | ||
619 | /// specified it is the root directory | ||
620 | /// for any relative paths in <paramref name="fileNames"/>.</param> | ||
621 | /// <param name="fileNames">A mapping from internal file paths to | ||
622 | /// external file paths.</param> | ||
623 | /// <param name="compLevel">The compression level used when creating | ||
624 | /// the archive.</param> | ||
625 | /// <param name="progressHandler">Handler for receiving progress information; | ||
626 | /// this may be null if progress is not desired.</param> | ||
627 | public void PackFileSet( | ||
628 | string sourceDirectory, | ||
629 | IDictionary<string, string> fileNames, | ||
630 | CompressionLevel compLevel, | ||
631 | EventHandler<ArchiveProgressEventArgs> progressHandler) | ||
632 | { | ||
633 | if (fileNames == null) | ||
634 | { | ||
635 | throw new ArgumentNullException("fileNames"); | ||
636 | } | ||
637 | |||
638 | string[] fileNamesArray = new string[fileNames.Count]; | ||
639 | fileNames.Keys.CopyTo(fileNamesArray, 0); | ||
640 | |||
641 | using (CompressionEngine compressionEngine = this.CreateCompressionEngine()) | ||
642 | { | ||
643 | compressionEngine.Progress += progressHandler; | ||
644 | ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext( | ||
645 | this.FullName, sourceDirectory, fileNames); | ||
646 | streamContext.EnableOffsetOpen = true; | ||
647 | compressionEngine.CompressionLevel = compLevel; | ||
648 | compressionEngine.Pack(streamContext, fileNamesArray); | ||
649 | } | ||
650 | } | ||
651 | |||
652 | /// <summary> | ||
653 | /// Given a directory, gets the relative paths of all files in the | ||
654 | /// directory, optionally including all subdirectories. | ||
655 | /// </summary> | ||
656 | /// <param name="dir">The directory to search.</param> | ||
657 | /// <param name="includeSubdirectories">True to include subdirectories | ||
658 | /// in the search.</param> | ||
659 | /// <returns>A list of file paths relative to the directory.</returns> | ||
660 | internal IList<string> GetRelativeFilePathsInDirectoryTree( | ||
661 | string dir, bool includeSubdirectories) | ||
662 | { | ||
663 | IList<string> fileList = new List<string>(); | ||
664 | this.RecursiveGetRelativeFilePathsInDirectoryTree( | ||
665 | dir, String.Empty, includeSubdirectories, fileList); | ||
666 | return fileList; | ||
667 | } | ||
668 | |||
669 | /// <summary> | ||
670 | /// Retrieves information about one file from this archive. | ||
671 | /// </summary> | ||
672 | /// <param name="path">Path of the file in the archive.</param> | ||
673 | /// <returns>File information, or null if the file was not found | ||
674 | /// in the archive.</returns> | ||
675 | internal ArchiveFileInfo GetFile(string path) | ||
676 | { | ||
677 | IList<ArchiveFileInfo> files = this.InternalGetFiles( | ||
678 | delegate(string match) | ||
679 | { | ||
680 | return String.Compare( | ||
681 | match, path, true, CultureInfo.InvariantCulture) == 0; | ||
682 | }); | ||
683 | return (files != null && files.Count > 0 ? files[0] : null); | ||
684 | } | ||
685 | |||
686 | /// <summary> | ||
687 | /// Creates a compression engine that does the low-level work for | ||
688 | /// this object. | ||
689 | /// </summary> | ||
690 | /// <returns>A new compression engine instance that matches the specific | ||
691 | /// subclass of archive.</returns> | ||
692 | /// <remarks> | ||
693 | /// Each instance will be <see cref="CompressionEngine.Dispose()"/>d | ||
694 | /// immediately after use. | ||
695 | /// </remarks> | ||
696 | protected abstract CompressionEngine CreateCompressionEngine(); | ||
697 | |||
698 | /// <summary> | ||
699 | /// Creates a case-insensitive dictionary mapping from one list of | ||
700 | /// strings to the other. | ||
701 | /// </summary> | ||
702 | /// <param name="keys">List of keys.</param> | ||
703 | /// <param name="values">List of values that are mapped 1-to-1 to | ||
704 | /// the keys.</param> | ||
705 | /// <returns>A filled dictionary of the strings.</returns> | ||
706 | private static IDictionary<string, string> CreateStringDictionary( | ||
707 | IList<string> keys, IList<string> values) | ||
708 | { | ||
709 | IDictionary<string, string> stringDict = | ||
710 | new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); | ||
711 | for (int i = 0; i < keys.Count; i++) | ||
712 | { | ||
713 | stringDict.Add(keys[i], values[i]); | ||
714 | } | ||
715 | |||
716 | return stringDict; | ||
717 | } | ||
718 | |||
719 | /// <summary> | ||
720 | /// Recursive-descent helper function for | ||
721 | /// GetRelativeFilePathsInDirectoryTree. | ||
722 | /// </summary> | ||
723 | /// <param name="dir">The root directory of the search.</param> | ||
724 | /// <param name="relativeDir">The relative directory to be | ||
725 | /// processed now.</param> | ||
726 | /// <param name="includeSubdirectories">True to descend into | ||
727 | /// subdirectories.</param> | ||
728 | /// <param name="fileList">List of files found so far.</param> | ||
729 | private void RecursiveGetRelativeFilePathsInDirectoryTree( | ||
730 | string dir, | ||
731 | string relativeDir, | ||
732 | bool includeSubdirectories, | ||
733 | IList<string> fileList) | ||
734 | { | ||
735 | foreach (string file in System.IO.Directory.GetFiles(dir)) | ||
736 | { | ||
737 | string fileName = Path.GetFileName(file); | ||
738 | fileList.Add(Path.Combine(relativeDir, fileName)); | ||
739 | } | ||
740 | |||
741 | if (includeSubdirectories) | ||
742 | { | ||
743 | foreach (string subDir in System.IO.Directory.GetDirectories(dir)) | ||
744 | { | ||
745 | string subDirName = Path.GetFileName(subDir); | ||
746 | this.RecursiveGetRelativeFilePathsInDirectoryTree( | ||
747 | Path.Combine(dir, subDirName), | ||
748 | Path.Combine(relativeDir, subDirName), | ||
749 | includeSubdirectories, | ||
750 | fileList); | ||
751 | } | ||
752 | } | ||
753 | } | ||
754 | |||
755 | /// <summary> | ||
756 | /// Uses a CompressionEngine to get ArchiveFileInfo objects from this | ||
757 | /// archive, and then associates them with this ArchiveInfo instance. | ||
758 | /// </summary> | ||
759 | /// <param name="fileFilter">Optional predicate that can determine | ||
760 | /// which files to process.</param> | ||
761 | /// <returns>A list of <see cref="ArchiveFileInfo"/> objects, each | ||
762 | /// containing information about a file in the archive.</returns> | ||
763 | private IList<ArchiveFileInfo> InternalGetFiles(Predicate<string> fileFilter) | ||
764 | { | ||
765 | using (CompressionEngine compressionEngine = this.CreateCompressionEngine()) | ||
766 | { | ||
767 | ArchiveFileStreamContext streamContext = | ||
768 | new ArchiveFileStreamContext(this.FullName, null, null); | ||
769 | streamContext.EnableOffsetOpen = true; | ||
770 | IList<ArchiveFileInfo> files = | ||
771 | compressionEngine.GetFileInfo(streamContext, fileFilter); | ||
772 | for (int i = 0; i < files.Count; i++) | ||
773 | { | ||
774 | files[i].Archive = this; | ||
775 | } | ||
776 | |||
777 | return files; | ||
778 | } | ||
779 | } | ||
780 | } | ||
781 | } | ||