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