// 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.Runtime.Serialization; using System.Diagnostics.CodeAnalysis; /// /// Abstract object representing a compressed file within an archive; /// provides operations for getting the file properties and unpacking /// the file. /// [Serializable] public abstract class ArchiveFileInfo : FileSystemInfo { private ArchiveInfo archiveInfo; private string name; private string path; private bool initialized; private bool exists; private int archiveNumber; private FileAttributes attributes; private DateTime lastWriteTime; private long length; /// /// Creates a new ArchiveFileInfo object representing a file within /// an archive in a specified path. /// /// An object representing the archive /// containing the file. /// The path to the file within the archive. /// Usually, this is a simple file name, but if the archive contains /// a directory structure this may include the directory. protected ArchiveFileInfo(ArchiveInfo archiveInfo, string filePath) : base() { if (filePath == null) { throw new ArgumentNullException("filePath"); } this.Archive = archiveInfo; this.name = System.IO.Path.GetFileName(filePath); this.path = System.IO.Path.GetDirectoryName(filePath); this.attributes = FileAttributes.Normal; this.lastWriteTime = DateTime.MinValue; } /// /// Creates a new ArchiveFileInfo object with all parameters specified; /// used by subclasses when reading the metadata out of an archive. /// /// The internal path and name of the file in /// the archive. /// The archive number where the file /// starts. /// The stored attributes of the file. /// The stored last write time of the /// file. /// The uncompressed size of the file. protected ArchiveFileInfo( string filePath, int archiveNumber, FileAttributes attributes, DateTime lastWriteTime, long length) : this(null, filePath) { this.exists = true; this.archiveNumber = archiveNumber; this.attributes = attributes; this.lastWriteTime = lastWriteTime; this.length = length; this.initialized = true; } /// /// Initializes a new instance of the ArchiveFileInfo class with /// serialized data. /// /// The SerializationInfo that holds the serialized /// object data about the exception being thrown. /// The StreamingContext that contains contextual /// information about the source or destination. protected ArchiveFileInfo(SerializationInfo info, StreamingContext context) : base(info, context) { this.archiveInfo = (ArchiveInfo) info.GetValue( "archiveInfo", typeof(ArchiveInfo)); this.name = info.GetString("name"); this.path = info.GetString("path"); this.initialized = info.GetBoolean("initialized"); this.exists = info.GetBoolean("exists"); this.archiveNumber = info.GetInt32("archiveNumber"); this.attributes = (FileAttributes) info.GetValue( "attributes", typeof(FileAttributes)); this.lastWriteTime = info.GetDateTime("lastWriteTime"); this.length = info.GetInt64("length"); } /// /// Gets the name of the file. /// /// The name of the file, not including any path. public override string Name { get { return this.name; } } /// /// Gets the internal path of the file in the archive. /// /// The internal path of the file in the archive, not including /// the file name. public string Path { get { return this.path; } } /// /// Gets the full path to the file. /// /// The full path to the file, including the full path to the /// archive, the internal path in the archive, and the file name. /// /// For example, the path "C:\archive.cab\file.txt" refers to /// a file "file.txt" inside the archive "archive.cab". /// public override string FullName { get { string fullName = System.IO.Path.Combine(this.Path, this.Name); if (this.Archive != null) { fullName = System.IO.Path.Combine(this.ArchiveName, fullName); } return fullName; } } /// /// Gets or sets the archive that contains this file. /// /// /// The ArchiveInfo instance that retrieved this file information -- this /// may be null if the ArchiveFileInfo object was returned directly from /// a stream. /// public ArchiveInfo Archive { get { return (ArchiveInfo) this.archiveInfo; } internal set { this.archiveInfo = value; // protected instance members inherited from FileSystemInfo: this.OriginalPath = (value != null ? value.FullName : null); this.FullPath = this.OriginalPath; } } /// /// Gets the full path of the archive that contains this file. /// /// The full path of the archive that contains this file. public string ArchiveName { get { return this.Archive != null ? this.Archive.FullName : null; } } /// /// Gets the number of the archive where this file starts. /// /// The number of the archive where this file starts. /// A single archive or the first archive in a chain is /// numbered 0. public int ArchiveNumber { get { return this.archiveNumber; } } /// /// Checks if the file exists within the archive. /// /// True if the file exists, false otherwise. public override bool Exists { get { if (!this.initialized) { this.Refresh(); } return this.exists; } } /// /// Gets the uncompressed size of the file. /// /// The uncompressed size of the file in bytes. public long Length { get { if (!this.initialized) { this.Refresh(); } return this.length; } } /// /// Gets the attributes of the file. /// /// The attributes of the file as stored in the archive. public new FileAttributes Attributes { get { if (!this.initialized) { this.Refresh(); } return this.attributes; } } /// /// Gets the last modification time of the file. /// /// The last modification time of the file as stored in the /// archive. public new DateTime LastWriteTime { get { if (!this.initialized) { this.Refresh(); } return this.lastWriteTime; } } /// /// Sets the SerializationInfo with information about the archive. /// /// The SerializationInfo that holds the serialized /// object data. /// The StreamingContext that contains contextual /// information about the source or destination. public override void GetObjectData( SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue("archiveInfo", this.archiveInfo); info.AddValue("name", this.name); info.AddValue("path", this.path); info.AddValue("initialized", this.initialized); info.AddValue("exists", this.exists); info.AddValue("archiveNumber", this.archiveNumber); info.AddValue("attributes", this.attributes); info.AddValue("lastWriteTime", this.lastWriteTime); info.AddValue("length", this.length); } /// /// Gets the full path to the file. /// /// The same as public override string ToString() { return this.FullName; } /// /// Deletes the file. NOT SUPPORTED. /// /// Files cannot be deleted /// from an existing archive. public override void Delete() { throw new NotSupportedException(); } /// /// Refreshes the attributes and other cached information about the file, /// by re-reading the information from the archive. /// public new void Refresh() { base.Refresh(); if (this.Archive != null) { string filePath = System.IO.Path.Combine(this.Path, this.Name); ArchiveFileInfo updatedFile = this.Archive.GetFile(filePath); if (updatedFile == null) { throw new FileNotFoundException( "File not found in archive.", filePath); } this.Refresh(updatedFile); } } /// /// Extracts the file. /// /// The destination path where the file /// will be extracted. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] public void CopyTo(string destFileName) { this.CopyTo(destFileName, false); } /// /// Extracts the file, optionally overwriting any existing file. /// /// The destination path where the file /// will be extracted. /// If true, /// will be overwritten if it exists. /// is false /// and exists. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] public void CopyTo(string destFileName, bool overwrite) { if (destFileName == null) { throw new ArgumentNullException("destFileName"); } if (!overwrite && File.Exists(destFileName)) { throw new IOException(); } if (this.Archive == null) { throw new InvalidOperationException(); } this.Archive.UnpackFile( System.IO.Path.Combine(this.Path, this.Name), destFileName); } /// /// Opens the archive file for reading without actually extracting the /// file to disk. /// /// /// A stream for reading directly from the packed file. Like any stream /// this should be closed/disposed as soon as it is no longer needed. /// public Stream OpenRead() { return this.Archive.OpenRead(System.IO.Path.Combine(this.Path, this.Name)); } /// /// Opens the archive file reading text with UTF-8 encoding without /// actually extracting the file to disk. /// /// /// A reader for reading text directly from the packed file. Like any reader /// this should be closed/disposed as soon as it is no longer needed. /// /// /// To open an archived text file with different encoding, use the /// method and pass the returned stream to one of /// the constructor overloads. /// public StreamReader OpenText() { return this.Archive.OpenText(System.IO.Path.Combine(this.Path, this.Name)); } /// /// Refreshes the information in this object with new data retrieved /// from an archive. /// /// Fresh instance for the same file just /// read from the archive. /// /// Subclasses may override this method to refresh sublcass fields. /// However they should always call the base implementation first. /// protected virtual void Refresh(ArchiveFileInfo newFileInfo) { this.exists = newFileInfo.exists; this.length = newFileInfo.length; this.attributes = newFileInfo.attributes; this.lastWriteTime = newFileInfo.lastWriteTime; } } }