// 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);
}
}
}