aboutsummaryrefslogtreecommitdiff
path: root/src/dtf/WixToolset.Dtf.Compression/ArchiveInfo.cs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/dtf/WixToolset.Dtf.Compression/ArchiveInfo.cs781
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
3namespace 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 /// &quot;*.txt&quot;.</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}