// Copyright 1998-2019 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 Tools.DotNETCommon { /// /// Representation of an absolute file path. Allows fast hashing and comparisons. /// [Serializable] public class FileReference : FileSystemReference, IEquatable { /// /// Dummy enum to allow invoking the constructor which takes a sanitized full path /// public enum Sanitize { None } /// /// Default constructor. /// /// Path to this file public FileReference(string InPath) : base(Path.GetFullPath(InPath)) { if(FullName[FullName.Length - 1] == '\\' || FullName[FullName.Length - 1] == '/') { throw new ArgumentException("File names may not be terminated by a path separator character"); } } /// /// Construct a FileReference from a FileInfo object. /// /// Path to this file public FileReference(FileInfo InInfo) : base(InInfo.FullName) { } /// /// Default constructor. /// /// The full sanitized path /// Dummary argument to use the sanitized overload public FileReference(string InFullName, Sanitize InSanitize) : base(InFullName) { } /// /// Create a FileReference from a string. If the string is null, returns a null FileReference. /// /// FileName for the string /// Returns a FileReference representing the given string, or null. public static FileReference FromString(string FileName) { if(String.IsNullOrEmpty(FileName)) { return null; } else { return new FileReference(FileName); } } /// /// 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, Sanitize.None); } /// /// Gets the directory containing this file /// /// A new directory object representing the directory containing this object public DirectoryReference Directory { get { return DirectoryReference.GetParentDirectory(this); } } /// /// 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, Sanitize.None); } /// /// 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, Sanitize.None); } /// /// 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.FullName.Equals(B.FullName, Comparison); } } /// /// 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 Comparer.GetHashCode(FullName); } /// /// 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, Sanitize.None); } /// /// Makes a file location writeable; /// /// Location of the file public static void MakeWriteable(FileReference Location) { if(Exists(Location)) { FileAttributes Attributes = GetAttributes(Location); if((Attributes & FileAttributes.ReadOnly) != 0) { SetAttributes(Location, Attributes & ~FileAttributes.ReadOnly); } } } /// /// Finds the correct case to match the location of this file on disk. Uses the given case for parts of the path that do not exist. /// /// The path to find the correct case for /// Location of the file with the correct case public static FileReference FindCorrectCase(FileReference Location) { return new FileReference(FileUtils.FindCorrectCase(Location.ToFileInfo())); } /// /// Constructs a FileInfo object from this reference /// /// New FileInfo object public FileInfo ToFileInfo() { return new FileInfo(FullName); } #region System.IO.File methods /// /// Copies a file from one location to another /// /// Location of the source file /// Location of the target file public static void Copy(FileReference SourceLocation, FileReference TargetLocation) { File.Copy(SourceLocation.FullName, TargetLocation.FullName); } /// /// Copies a file from one location to another /// /// Location of the source file /// Location of the target file /// Whether to overwrite the file in the target location public static void Copy(FileReference SourceLocation, FileReference TargetLocation, bool bOverwrite) { File.Copy(SourceLocation.FullName, TargetLocation.FullName, bOverwrite); } /// /// Deletes this file /// public static void Delete(FileReference Location) { File.Delete(Location.FullName); } /// /// Determines whether the given filename exists /// /// True if it exists, false otherwise public static bool Exists(FileReference Location) { return File.Exists(Location.FullName); } /// /// Gets the attributes for a file /// /// Location of the file /// Attributes for the file public static FileAttributes GetAttributes(FileReference Location) { return File.GetAttributes(Location.FullName); } /// /// Gets the time that the file was last written to /// /// Location of the file /// Last write time, in local time public static DateTime GetLastWriteTime(FileReference Location) { return File.GetLastWriteTime(Location.FullName); } /// /// Gets the time that the file was last written to /// /// Location of the file /// Last write time, in UTC time public static DateTime GetLastWriteTimeUtc(FileReference Location) { return File.GetLastWriteTimeUtc(Location.FullName); } /// /// Moves a file from one location to another /// /// Location of the source file /// Location of the target file public static void Move(FileReference SourceLocation, FileReference TargetLocation) { File.Move(SourceLocation.FullName, TargetLocation.FullName); } /// /// Opens a FileStream on the specified path with read/write access /// /// Location of the file /// Mode to use when opening the file /// New filestream for the given file public static FileStream Open(FileReference Location, FileMode Mode) { return File.Open(Location.FullName, Mode); } /// /// Opens a FileStream on the specified path /// /// Location of the file /// Mode to use when opening the file /// Sharing mode for the new file /// New filestream for the given file public static FileStream Open(FileReference Location, FileMode Mode, FileAccess Access) { return File.Open(Location.FullName, Mode, Access); } /// /// Opens a FileStream on the specified path /// /// Location of the file /// Mode to use when opening the file /// Access mode for the new file /// Sharing mode for the open file /// New filestream for the given file public static FileStream Open(FileReference Location, FileMode Mode, FileAccess Access, FileShare Share) { return File.Open(Location.FullName, Mode, Access, Share); } /// /// Reads the contents of a file /// /// Location of the file /// Byte array containing the contents of the file public static byte[] ReadAllBytes(FileReference Location) { return File.ReadAllBytes(Location.FullName); } /// /// Reads the contents of a file /// /// Location of the file /// Contents of the file as a single string public static string ReadAllText(FileReference Location) { return File.ReadAllText(Location.FullName); } /// /// Reads the contents of a file /// /// Location of the file /// Encoding of the file /// Contents of the file as a single string public static string ReadAllText(FileReference Location, Encoding Encoding) { return File.ReadAllText(Location.FullName, Encoding); } /// /// Reads the contents of a file /// /// Location of the file /// String array containing the contents of the file public static string[] ReadAllLines(FileReference Location) { return File.ReadAllLines(Location.FullName); } /// /// Reads the contents of a file /// /// Location of the file /// The encoding to use when parsing the file /// String array containing the contents of the file public static string[] ReadAllLines(FileReference Location, Encoding Encoding) { return File.ReadAllLines(Location.FullName, Encoding); } /// /// Sets the attributes for a file /// /// Location of the file /// New attributes for the file public static void SetAttributes(FileReference Location, FileAttributes Attributes) { File.SetAttributes(Location.FullName, Attributes); } /// /// Sets the time that the file was last written to /// /// Location of the file /// Last write time, in local time public static void SetLastWriteTime(FileReference Location, DateTime LastWriteTime) { File.SetLastWriteTime(Location.FullName, LastWriteTime); } /// /// Sets the time that the file was last written to /// /// Location of the file /// Last write time, in UTC time public static void SetLastWriteTimeUtc(FileReference Location, DateTime LastWriteTimeUtc) { File.SetLastWriteTimeUtc(Location.FullName, LastWriteTimeUtc); } /// /// Writes the contents of a file /// /// Location of the file /// Contents of the file public static void WriteAllBytes(FileReference Location, byte[] Contents) { File.WriteAllBytes(Location.FullName, Contents); } /// /// Writes the data to the given file, if it's different from what's there already /// /// Location of the file /// Contents of the file public static void WriteAllBytesIfDifferent(FileReference Location, byte[] Contents) { if(FileReference.Exists(Location)) { byte[] CurrentContents = FileReference.ReadAllBytes(Location); if(ArrayUtils.ByteArraysEqual(Contents, CurrentContents)) { return; } } WriteAllBytes(Location, Contents); } /// /// Writes the contents of a file /// /// Location of the file /// Contents of the file public static void WriteAllLines(FileReference Location, IEnumerable Contents) { File.WriteAllLines(Location.FullName, Contents); } /// /// Writes the contents of a file /// /// Location of the file /// Contents of the file /// The encoding to use when parsing the file public static void WriteAllLines(FileReference Location, IEnumerable Contents, Encoding Encoding) { File.WriteAllLines(Location.FullName, Contents, Encoding); } /// /// Writes the contents of a file /// /// Location of the file /// Contents of the file public static void WriteAllLines(FileReference Location, string[] Contents) { File.WriteAllLines(Location.FullName, Contents); } /// /// Writes the contents of a file /// /// Location of the file /// Contents of the file /// The encoding to use when parsing the file public static void WriteAllLines(FileReference Location, string[] Contents, Encoding Encoding) { File.WriteAllLines(Location.FullName, Contents, Encoding); } /// /// Writes the contents of a file /// /// Location of the file /// Contents of the file public static void WriteAllText(FileReference Location, string Contents) { File.WriteAllText(Location.FullName, Contents); } /// /// Writes the contents of a file /// /// Location of the file /// Contents of the file /// The encoding to use when parsing the file public static void WriteAllText(FileReference Location, string Contents, Encoding Encoding) { File.WriteAllText(Location.FullName, Contents, Encoding); } #endregion } /// /// Extension methods for FileReference functionality /// public static class FileReferenceExtensionMethods { /// /// Manually serialize a file reference to a binary stream. /// /// Binary writer to write to /// The file reference to write 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 : new FileReference(FullName, FileReference.Sanitize.None); } /// /// 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; } } /// /// Writes a FileReference to a binary archive /// /// The writer to output data to /// The file reference to write public static void WriteFileReference(this BinaryArchiveWriter Writer, FileReference File) { if(File == null) { Writer.WriteString(null); } else { Writer.WriteString(File.FullName); } } /// /// Reads a FileReference from a binary archive /// /// Reader to serialize data from /// New file reference instance public static FileReference ReadFileReference(this BinaryArchiveReader Reader) { string FullName = Reader.ReadString(); if(FullName == null) { return null; } else { return new FileReference(FullName, FileReference.Sanitize.None); } } } }