| 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 | } |