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