// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UnrealBuildTool
{
///
/// Base class for file system objects (files or directories).
///
[Serializable]
public abstract class FileSystemReference
{
///
/// The path to this object. Stored as an absolute path, with O/S preferred separator characters, and no trailing slash for directories.
///
public readonly string FullName;
///
/// The canonical full name for this object.
///
public readonly string CanonicalName;
///
/// Constructs a filesystem object for the given path.
///
public FileSystemReference(string InPath)
{
FullName = Path.GetFullPath(InPath).TrimEnd(Path.DirectorySeparatorChar);
CanonicalName = FullName.ToLowerInvariant();
}
///
/// Constructs a reference from the given FileSystemInfo.
///
public FileSystemReference(FileSystemInfo InInfo)
{
FullName = InInfo.FullName;
CanonicalName = FullName.ToLowerInvariant();
}
///
/// Direct constructor for a path
///
protected FileSystemReference(string InFullName, string InCanonicalName)
{
FullName = InFullName;
CanonicalName = InCanonicalName;
}
///
/// Create a full path by concatenating multiple strings
///
///
static protected string CombineStrings(DirectoryReference BaseDirectory, params string[] Fragments)
{
// Get the initial string to append to, and strip any root directory suffix from it
StringBuilder NewFullName = new StringBuilder(BaseDirectory.FullName);
if (NewFullName.Length > 0 && NewFullName[NewFullName.Length - 1] == Path.DirectorySeparatorChar)
{
NewFullName.Remove(NewFullName.Length - 1, 1);
}
// Scan through the fragments to append, appending them to a string and updating the base length as we go
foreach (string Fragment in Fragments)
{
// Check if this fragment is an absolute path
if ((Fragment.Length >= 2 && Fragment[1] == ':') || (Fragment.Length >= 1 && (Fragment[0] == Path.DirectorySeparatorChar || Fragment[0] == Path.AltDirectorySeparatorChar)))
{
// It is. Reset the new name to the full version of this path.
NewFullName.Clear();
NewFullName.Append(Path.GetFullPath(Fragment).TrimEnd(Path.DirectorySeparatorChar));
}
else
{
// Append all the parts of this fragment to the end of the existing path.
int StartIdx = 0;
while (StartIdx < Fragment.Length)
{
// Find the end of this fragment. We may have been passed multiple paths in the same string.
int EndIdx = StartIdx;
while (EndIdx < Fragment.Length && Fragment[EndIdx] != Path.DirectorySeparatorChar && Fragment[EndIdx] != Path.AltDirectorySeparatorChar)
{
EndIdx++;
}
// Ignore any empty sections, like leading or trailing slashes, and '.' directory references.
int Length = EndIdx - StartIdx;
if (Length == 0)
{
// Multiple directory separators in a row; illegal.
throw new ArgumentException("Path fragment '{0}' contains invalid directory separators.");
}
else if (Length == 2 && Fragment[StartIdx] == '.' && Fragment[StartIdx + 1] == '.')
{
// Remove the last directory name
for (int SeparatorIdx = NewFullName.Length - 1; SeparatorIdx >= 0; SeparatorIdx--)
{
if (NewFullName[SeparatorIdx] == Path.DirectorySeparatorChar)
{
NewFullName.Remove(SeparatorIdx, NewFullName.Length - SeparatorIdx);
break;
}
}
}
else if (Length != 1 || Fragment[StartIdx] != '.')
{
// Append this fragment
NewFullName.Append(Path.DirectorySeparatorChar);
NewFullName.Append(Fragment, StartIdx, Length);
}
// Move to the next part
StartIdx = EndIdx + 1;
}
}
}
// Append the directory separator
if (NewFullName.Length == 0 || (NewFullName.Length == 2 && NewFullName[1] == ':'))
{
NewFullName.Append(Path.DirectorySeparatorChar);
}
// Set the new path variables
return NewFullName.ToString();
}
///
/// Checks whether this name has the given extension.
///
/// The extension to check
/// True if this name has the given extension, false otherwise
public bool HasExtension(string Extension)
{
if (Extension.Length > 0 && Extension[0] != '.')
{
return HasExtension("." + Extension);
}
else
{
return CanonicalName.EndsWith(Extension.ToLowerInvariant());
}
}
///
/// Determines if the given object is at or under the given directory
///
///
///
public bool IsUnderDirectory(DirectoryReference Other)
{
return CanonicalName.StartsWith(Other.CanonicalName) && (CanonicalName.Length == Other.CanonicalName.Length || CanonicalName[Other.CanonicalName.Length] == Path.DirectorySeparatorChar);
}
///
/// Creates a relative path from the given base directory
///
/// The directory to create a relative path from
/// A relative path from the given directory
public string MakeRelativeTo(DirectoryReference Directory)
{
// Find how much of the path is common between the two paths. This length does not include a trailing directory separator character.
int CommonDirectoryLength = -1;
for (int Idx = 0; ; Idx++)
{
if (Idx == CanonicalName.Length)
{
// The two paths are identical. Just return the "." character.
if (Idx == Directory.CanonicalName.Length)
{
return ".";
}
// Check if we're finishing on a complete directory name
if (Directory.CanonicalName[Idx] == Path.DirectorySeparatorChar)
{
CommonDirectoryLength = Idx;
}
break;
}
else if (Idx == Directory.CanonicalName.Length)
{
// Check whether the end of the directory name coincides with a boundary for the current name.
if (CanonicalName[Idx] == Path.DirectorySeparatorChar)
{
CommonDirectoryLength = Idx;
}
break;
}
else
{
// Check the two paths match, and bail if they don't. Increase the common directory length if we've reached a separator.
if (CanonicalName[Idx] != Directory.CanonicalName[Idx])
{
break;
}
if (CanonicalName[Idx] == Path.DirectorySeparatorChar)
{
CommonDirectoryLength = Idx;
}
}
}
// If there's no relative path, just return the absolute path
if (CommonDirectoryLength == -1)
{
return FullName;
}
// Append all the '..' separators to get back to the common directory, then the rest of the string to reach the target item
StringBuilder Result = new StringBuilder();
for (int Idx = CommonDirectoryLength + 1; Idx < Directory.CanonicalName.Length; Idx++)
{
// Move up a directory
Result.Append("..");
Result.Append(Path.DirectorySeparatorChar);
// Scan to the next directory separator
while (Idx < Directory.CanonicalName.Length && Directory.CanonicalName[Idx] != Path.DirectorySeparatorChar)
{
Idx++;
}
}
if (CommonDirectoryLength + 1 < FullName.Length)
{
Result.Append(FullName, CommonDirectoryLength + 1, FullName.Length - CommonDirectoryLength - 1);
}
return Result.ToString();
}
///
/// Returns a string representation of this filesystem object
///
/// Full path to the object
public override string ToString()
{
return FullName;
}
}
///
/// Representation of an absolute directory path. Allows fast hashing and comparisons.
///
[Serializable]
public class DirectoryReference : FileSystemReference, IEquatable
{
///
/// Default constructor.
///
/// Path to this directory.
public DirectoryReference(string InPath)
: base(InPath)
{
}
///
/// Construct a DirectoryReference from a DirectoryInfo object.
///
/// Path to this file
public DirectoryReference(DirectoryInfo InInfo)
: base(InInfo)
{
}
///
/// Constructor for creating a directory object directly from two strings.
///
///
///
protected DirectoryReference(string InFullName, string InCanonicalName)
: base(InFullName, InCanonicalName)
{
}
///
/// Gets the top level directory name
///
/// The name of the directory
public string GetDirectoryName()
{
return Path.GetFileName(FullName);
}
///
/// Gets the directory containing this object
///
/// A new directory object representing the directory containing this object
public DirectoryReference ParentDirectory
{
get
{
if (IsRootDirectory())
{
return null;
}
int ParentLength = CanonicalName.LastIndexOf(Path.DirectorySeparatorChar);
if (ParentLength == 2 && CanonicalName[1] == ':')
{
ParentLength++;
}
return new DirectoryReference(FullName.Substring(0, ParentLength), CanonicalName.Substring(0, ParentLength));
}
}
///
/// Gets the parent directory for a file
///
/// The file to get directory for
/// The full directory name containing the given file
public static DirectoryReference GetParentDirectory(FileReference File)
{
int ParentLength = File.CanonicalName.LastIndexOf(Path.DirectorySeparatorChar);
return new DirectoryReference(File.FullName.Substring(0, ParentLength), File.CanonicalName.Substring(0, ParentLength));
}
///
/// Creates the directory
///
public void CreateDirectory()
{
Directory.CreateDirectory(FullName);
}
///
/// Checks whether the directory exists
///
/// True if this directory exists
public bool Exists()
{
return Directory.Exists(FullName);
}
///
/// Enumerate files from a given directory
///
/// Sequence of file references
public IEnumerable EnumerateFileReferences()
{
foreach (string FileName in Directory.EnumerateFiles(FullName))
{
yield return FileReference.MakeFromNormalizedFullPath(FileName);
}
}
///
/// Enumerate files from a given directory
///
/// Sequence of file references
public IEnumerable EnumerateFileReferences(string Pattern)
{
foreach (string FileName in Directory.EnumerateFiles(FullName, Pattern))
{
yield return FileReference.MakeFromNormalizedFullPath(FileName);
}
}
///
/// Enumerate files from a given directory
///
/// Sequence of file references
public IEnumerable EnumerateFileReferences(string Pattern, SearchOption Option)
{
foreach (string FileName in Directory.EnumerateFiles(FullName, Pattern, Option))
{
yield return FileReference.MakeFromNormalizedFullPath(FileName);
}
}
///
/// Enumerate subdirectories in a given directory
///
/// Sequence of directory references
public IEnumerable EnumerateDirectoryReferences()
{
foreach (string DirectoryName in Directory.EnumerateDirectories(FullName))
{
yield return DirectoryReference.MakeFromNormalizedFullPath(DirectoryName);
}
}
///
/// Enumerate subdirectories in a given directory
///
/// Sequence of directory references
public IEnumerable EnumerateDirectoryReferences(string Pattern)
{
foreach (string DirectoryName in Directory.EnumerateDirectories(FullName, Pattern))
{
yield return DirectoryReference.MakeFromNormalizedFullPath(DirectoryName);
}
}
///
/// Enumerate subdirectories in a given directory
///
/// Sequence of directory references
public IEnumerable EnumerateDirectoryReferences(string Pattern, SearchOption Option)
{
foreach (string DirectoryName in Directory.EnumerateDirectories(FullName, Pattern, Option))
{
yield return DirectoryReference.MakeFromNormalizedFullPath(DirectoryName);
}
}
///
/// Determines whether this path represents a root directory in the filesystem
///
/// True if this path is a root directory, false otherwise
public bool IsRootDirectory()
{
return CanonicalName[CanonicalName.Length - 1] == Path.DirectorySeparatorChar;
}
///
/// Combine several fragments with a base directory, to form a new directory name
///
/// The base directory
/// Fragments to combine with the base directory
/// The new directory name
public static DirectoryReference Combine(DirectoryReference BaseDirectory, params string[] Fragments)
{
string FullName = FileSystemReference.CombineStrings(BaseDirectory, Fragments);
return new DirectoryReference(FullName, FullName.ToLowerInvariant());
}
///
/// Compares two filesystem object names for equality. Uses the canonical name representation, not the display name representation.
///
/// First object to compare.
/// Second object to compare.
/// True if the names represent the same object, false otherwise
public static bool operator ==(DirectoryReference A, DirectoryReference B)
{
if ((object)A == null)
{
return (object)B == null;
}
else
{
return (object)B != null && A.CanonicalName == B.CanonicalName;
}
}
///
/// Compares two filesystem object names for inequality. Uses the canonical name representation, not the display name representation.
///
/// First object to compare.
/// Second object to compare.
/// False if the names represent the same object, true otherwise
public static bool operator !=(DirectoryReference A, DirectoryReference B)
{
return !(A == B);
}
///
/// Compares against another object for equality.
///
/// other instance to compare.
/// True if the names represent the same object, false otherwise
public override bool Equals(object Obj)
{
return (Obj is DirectoryReference) && ((DirectoryReference)Obj) == this;
}
///
/// Compares against another object for equality.
///
/// other instance to compare.
/// True if the names represent the same object, false otherwise
public bool Equals(DirectoryReference Obj)
{
return Obj == this;
}
///
/// Returns a hash code for this object
///
///
public override int GetHashCode()
{
return CanonicalName.GetHashCode();
}
///
/// Helper function to create a remote directory reference. Unlike normal DirectoryReference objects, these aren't converted to a full path in the local filesystem.
///
/// The absolute path in the remote file system
/// New directory reference
public static DirectoryReference MakeRemote(string AbsolutePath)
{
return new DirectoryReference(AbsolutePath, AbsolutePath.ToLowerInvariant());
}
///
/// Helper function to create a directory reference from a raw platform path. The path provided *MUST* be exactly the same as that returned by Path.GetFullPath().
///
/// The absolute path in the file system
/// New file reference
public static DirectoryReference MakeFromNormalizedFullPath(string AbsolutePath)
{
return new DirectoryReference(AbsolutePath, AbsolutePath.ToLowerInvariant());
}
///
/// Gets the parent directory for a file, or returns null if it's null.
///
/// The file to create a directory reference for
/// The directory containing the file
public static DirectoryReference FromFile(FileReference File)
{
return (File == null)? null : File.Directory;
}
}
///
/// Representation of an absolute file path. Allows fast hashing and comparisons.
///
[Serializable]
public class FileReference : FileSystemReference, IEquatable
{
///
/// Default constructor.
///
/// Path to this file
public FileReference(string InPath)
: base(InPath)
{
}
///
/// Construct a FileReference from a FileInfo object.
///
/// Path to this file
public FileReference(FileInfo InInfo)
: base(InInfo)
{
}
///
/// Default constructor.
///
/// Path to this file
protected FileReference(string InFullName, string InCanonicalName)
: base(InFullName, InCanonicalName)
{
}
///
/// Gets the file name without path information
///
/// A string containing the file name
public string GetFileName()
{
return Path.GetFileName(FullName);
}
///
/// Gets the file name without path information or an extension
///
/// A string containing the file name without an extension
public string GetFileNameWithoutExtension()
{
return Path.GetFileNameWithoutExtension(FullName);
}
///
/// Gets the file name without path or any extensions
///
/// A string containing the file name without an extension
public string GetFileNameWithoutAnyExtensions()
{
int StartIdx = FullName.LastIndexOf(Path.DirectorySeparatorChar) + 1;
int EndIdx = FullName.IndexOf('.', StartIdx);
if (EndIdx < StartIdx)
{
return FullName.Substring(StartIdx);
}
else
{
return FullName.Substring(StartIdx, EndIdx - StartIdx);
}
}
///
/// Gets the extension for this filename
///
/// A string containing the extension of this filename
public string GetExtension()
{
return Path.GetExtension(FullName);
}
///
/// Change the file's extension to something else
///
/// The new extension
/// A FileReference with the same path and name, but with the new extension
public FileReference ChangeExtension(string Extension)
{
string NewFullName = Path.ChangeExtension(FullName, Extension);
return new FileReference(NewFullName, NewFullName.ToLowerInvariant());
}
///
/// Gets the directory containing this file
///
/// A new directory object representing the directory containing this object
public DirectoryReference Directory
{
get { return DirectoryReference.GetParentDirectory(this); }
}
///
/// Determines whether the given filename exists
///
/// True if it exists, false otherwise
public bool Exists()
{
return File.Exists(FullName);
}
///
/// Deletes this file
///
public void Delete()
{
File.Delete(FullName);
}
///
/// Combine several fragments with a base directory, to form a new filename
///
/// The base directory
/// Fragments to combine with the base directory
/// The new file name
public static FileReference Combine(DirectoryReference BaseDirectory, params string[] Fragments)
{
string FullName = FileSystemReference.CombineStrings(BaseDirectory, Fragments);
return new FileReference(FullName, FullName.ToLowerInvariant());
}
///
/// Append a string to the end of a filename
///
/// The base file reference
/// Suffix to be appended
/// The new file reference
public static FileReference operator +(FileReference A, string B)
{
return new FileReference(A.FullName + B, A.CanonicalName + B.ToLowerInvariant());
}
///
/// Compares two filesystem object names for equality. Uses the canonical name representation, not the display name representation.
///
/// First object to compare.
/// Second object to compare.
/// True if the names represent the same object, false otherwise
public static bool operator ==(FileReference A, FileReference B)
{
if ((object)A == null)
{
return (object)B == null;
}
else
{
return (object)B != null && A.CanonicalName == B.CanonicalName;
}
}
///
/// Compares two filesystem object names for inequality. Uses the canonical name representation, not the display name representation.
///
/// First object to compare.
/// Second object to compare.
/// False if the names represent the same object, true otherwise
public static bool operator !=(FileReference A, FileReference B)
{
return !(A == B);
}
///
/// Compares against another object for equality.
///
/// other instance to compare.
/// True if the names represent the same object, false otherwise
public override bool Equals(object Obj)
{
return (Obj is FileReference) && ((FileReference)Obj) == this;
}
///
/// Compares against another object for equality.
///
/// other instance to compare.
/// True if the names represent the same object, false otherwise
public bool Equals(FileReference Obj)
{
return Obj == this;
}
///
/// Returns a hash code for this object
///
///
public override int GetHashCode()
{
return CanonicalName.GetHashCode();
}
///
/// Helper function to create a remote file reference. Unlike normal FileReference objects, these aren't converted to a full path in the local filesystem, but are
/// left as they are passed in.
///
/// The absolute path in the remote file system
/// New file reference
public static FileReference MakeRemote(string AbsolutePath)
{
return new FileReference(AbsolutePath, AbsolutePath.ToLowerInvariant());
}
///
/// Helper function to create a file reference from a raw platform path. The path provided *MUST* be exactly the same as that returned by Path.GetFullPath().
///
/// The absolute path in the file system
/// New file reference
public static FileReference MakeFromNormalizedFullPath(string AbsolutePath)
{
return new FileReference(AbsolutePath, AbsolutePath.ToLowerInvariant());
}
}
static class FileReferenceExtensionMethods
{
///
/// Manually serialize a file reference to a binary stream.
///
/// Binary writer to write to
public static void Write(this BinaryWriter Writer, FileReference File)
{
Writer.Write((File == null) ? String.Empty : File.FullName);
}
///
/// Serializes a file reference, using a lookup table to avoid serializing the same name more than once.
///
/// The writer to save this reference to
/// A file reference to output; may be null
/// A lookup table that caches previous files that have been output, and maps them to unique id's.
public static void Write(this BinaryWriter Writer, FileReference File, Dictionary FileToUniqueId)
{
int UniqueId;
if (File == null)
{
Writer.Write(-1);
}
else if (FileToUniqueId.TryGetValue(File, out UniqueId))
{
Writer.Write(UniqueId);
}
else
{
Writer.Write(FileToUniqueId.Count);
Writer.Write(File);
FileToUniqueId.Add(File, FileToUniqueId.Count);
}
}
///
/// Manually deserialize a file reference from a binary stream.
///
/// Binary reader to read from
/// New FileReference object
public static FileReference ReadFileReference(this BinaryReader Reader)
{
string FullName = Reader.ReadString();
return (FullName.Length == 0) ? null : FileReference.MakeFromNormalizedFullPath(FullName);
}
///
/// Deserializes a file reference, using a lookup table to avoid writing the same name more than once.
///
/// The source to read from
/// List of previously read file references. The index into this array is used in place of subsequent ocurrences of the file.
/// The file reference that was read
public static FileReference ReadFileReference(this BinaryReader Reader, List UniqueFiles)
{
int UniqueId = Reader.ReadInt32();
if (UniqueId == -1)
{
return null;
}
else if (UniqueId < UniqueFiles.Count)
{
return UniqueFiles[UniqueId];
}
else
{
FileReference Result = Reader.ReadFileReference();
UniqueFiles.Add(Result);
return Result;
}
}
}
}