// 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. namespace WixToolset.Dtf.Compression { using System; using System.IO; using System.Collections.Generic; using System.Globalization; /// /// Base class for an engine capable of packing and unpacking a particular /// compressed file format. /// public abstract class CompressionEngine : IDisposable { private CompressionLevel compressionLevel; private bool dontUseTempFiles; /// /// Creates a new instance of the compression engine base class. /// protected CompressionEngine() { this.compressionLevel = CompressionLevel.Normal; } /// /// Disposes the compression engine. /// ~CompressionEngine() { this.Dispose(false); } /// /// Occurs when the compression engine reports progress in packing /// or unpacking an archive. /// /// public event EventHandler Progress; /// /// Gets or sets a flag indicating whether temporary files are created /// and used during compression. /// /// True if temporary files are used; false if compression is done /// entirely in-memory. /// The value of this property is true by default. Using temporary /// files can greatly reduce the memory requirement of compression, /// especially when compressing large archives. However, setting this property /// to false may yield slightly better performance when creating small /// archives. Or it may be necessary if the process does not have sufficient /// privileges to create temporary files. public bool UseTempFiles { get { return !this.dontUseTempFiles; } set { this.dontUseTempFiles = !value; } } /// /// Compression level to use when compressing files. /// /// A compression level ranging from minimum to maximum compression, /// or no compression. public CompressionLevel CompressionLevel { get { return this.compressionLevel; } set { this.compressionLevel = value; } } /// /// Disposes of resources allocated by the compression engine. /// public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } /// /// Creates an archive. /// /// A context interface to handle opening /// and closing of archive and file streams. /// The paths of the files in the archive /// (not external file paths). /// The archive could not be /// created. /// /// The stream context implementation may provide a mapping from the /// file paths within the archive to the external file paths. /// public void Pack(IPackStreamContext streamContext, IEnumerable files) { if (files == null) { throw new ArgumentNullException("files"); } this.Pack(streamContext, files, 0); } /// /// Creates an archive or chain of archives. /// /// A context interface to handle opening /// and closing of archive and file streams. /// The paths of the files in the archive (not /// external file paths). /// The maximum number of bytes for one /// archive before the contents are chained to the next archive, or zero /// for unlimited archive size. /// The archive could not be /// created. /// /// The stream context implementation may provide a mapping from the file /// paths within the archive to the external file paths. /// public abstract void Pack( IPackStreamContext streamContext, IEnumerable files, long maxArchiveSize); /// /// Checks whether a Stream begins with a header that indicates /// it is a valid archive. /// /// Stream for reading the archive file. /// True if the stream is a valid archive /// (with no offset); false otherwise. public abstract bool IsArchive(Stream stream); /// /// Gets the offset of an archive that is positioned 0 or more bytes /// from the start of the Stream. /// /// A stream for reading the archive. /// The offset in bytes of the archive, /// or -1 if no archive is found in the Stream. /// The archive must begin on a 4-byte boundary. public virtual long FindArchiveOffset(Stream stream) { if (stream == null) { throw new ArgumentNullException("stream"); } long sectionSize = 4; long length = stream.Length; for (long offset = 0; offset <= length - sectionSize; offset += sectionSize) { stream.Seek(offset, SeekOrigin.Begin); if (this.IsArchive(stream)) { return offset; } } return -1; } /// /// Gets information about all files in an archive stream. /// /// A stream for reading the archive. /// Information about all files in the archive stream. /// The stream is not a valid /// archive. public IList GetFileInfo(Stream stream) { return this.GetFileInfo(new BasicUnpackStreamContext(stream), null); } /// /// Gets information about files in an archive or archive chain. /// /// A context interface to handle opening /// and closing of archive and file streams. /// A predicate that can determine /// which files to process, optional. /// Information about files in the archive stream. /// The archive provided /// by the stream context is not valid. /// /// The predicate takes an internal file /// path and returns true to include the file or false to exclude it. /// public abstract IList GetFileInfo( IUnpackStreamContext streamContext, Predicate fileFilter); /// /// Gets the list of files in an archive Stream. /// /// A stream for reading the archive. /// A list of the paths of all files contained in the /// archive. /// The stream is not a valid /// archive. public IList GetFiles(Stream stream) { return this.GetFiles(new BasicUnpackStreamContext(stream), null); } /// /// Gets the list of files in an archive or archive chain. /// /// A context interface to handle opening /// and closing of archive and file streams. /// A predicate that can determine /// which files to process, optional. /// An array containing the names of all files contained in /// the archive or archive chain. /// The archive provided /// by the stream context is not valid. /// /// The predicate takes an internal file /// path and returns true to include the file or false to exclude it. /// public IList GetFiles( IUnpackStreamContext streamContext, Predicate fileFilter) { if (streamContext == null) { throw new ArgumentNullException("streamContext"); } IList files = this.GetFileInfo(streamContext, fileFilter); IList fileNames = new List(files.Count); for (int i = 0; i < files.Count; i++) { fileNames.Add(files[i].Name); } return fileNames; } /// /// Reads a single file from an archive stream. /// /// A stream for reading the archive. /// The path of the file within the archive /// (not the external file path). /// A stream for reading the extracted file, or null /// if the file does not exist in the archive. /// The stream is not a valid /// archive. /// The entire extracted file is cached in memory, so this /// method requires enough free memory to hold the file. public Stream Unpack(Stream stream, string path) { if (stream == null) { throw new ArgumentNullException("stream"); } if (path == null) { throw new ArgumentNullException("path"); } BasicUnpackStreamContext streamContext = new BasicUnpackStreamContext(stream); this.Unpack( streamContext, delegate(string match) { return String.Compare( match, path, true, CultureInfo.InvariantCulture) == 0; }); Stream extractStream = streamContext.FileStream; if (extractStream != null) { extractStream.Position = 0; } return extractStream; } /// /// Extracts files from an archive or archive chain. /// /// A context interface to handle opening /// and closing of archive and file streams. /// An optional predicate that can determine /// which files to process. /// The archive provided /// by the stream context is not valid. /// /// The predicate takes an internal file /// path and returns true to include the file or false to exclude it. /// public abstract void Unpack( IUnpackStreamContext streamContext, Predicate fileFilter); /// /// Called by sublcasses to distribute a packing or unpacking progress /// event to listeners. /// /// Event details. protected void OnProgress(ArchiveProgressEventArgs e) { if (this.Progress != null) { this.Progress(this, e); } } /// /// Disposes of resources allocated by the compression engine. /// /// If true, the method has been called /// directly or indirectly by a user's code, so managed and unmanaged /// resources will be disposed. If false, the method has been called by /// the runtime from inside the finalizer, and only unmanaged resources /// will be disposed. protected virtual void Dispose(bool disposing) { } /// /// Compresion utility function for converting old-style /// date and time values to a DateTime structure. /// public static void DosDateAndTimeToDateTime( short dosDate, short dosTime, out DateTime dateTime) { if (dosDate == 0 && dosTime == 0) { dateTime = DateTime.MinValue; } else { long fileTime; SafeNativeMethods.DosDateTimeToFileTime(dosDate, dosTime, out fileTime); dateTime = DateTime.FromFileTimeUtc(fileTime); dateTime = new DateTime(dateTime.Ticks, DateTimeKind.Local); } } /// /// Compresion utility function for converting a DateTime structure /// to old-style date and time values. /// public static void DateTimeToDosDateAndTime( DateTime dateTime, out short dosDate, out short dosTime) { dateTime = new DateTime(dateTime.Ticks, DateTimeKind.Utc); long filetime = dateTime.ToFileTimeUtc(); SafeNativeMethods.FileTimeToDosDateTime(ref filetime, out dosDate, out dosTime); } } }