// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using EpicGames.Core; namespace UnrealBuildBase { /// /// Stores the state of a directory. May or may not exist. /// public class DirectoryItem { /// /// Full path to the directory on disk /// public readonly DirectoryReference Location; /// /// Cached value for whether the directory exists /// DirectoryInfo Info; /// /// Cached map of name to subdirectory item /// Dictionary? Directories; /// /// Cached map of name to file /// Dictionary? Files; /// /// Global map of location to item /// static ConcurrentDictionary LocationToItem = new ConcurrentDictionary(); /// /// Constructor /// /// Path to this directory /// Information about this directory private DirectoryItem(DirectoryReference Location, DirectoryInfo Info) { this.Location = Location; this.Info = Info; } /// /// The name of this directory /// public string Name { get { return Info.Name; } } /// /// The full name of this directory /// public string FullName { get { return Location.FullName; } } /// /// Whether the directory exists or not /// public bool Exists { get { return Info.Exists; } } /// /// The last write time of the file. /// public DateTime LastWriteTimeUtc { get { return Info.LastWriteTimeUtc; } } /// /// Gets the parent directory item /// public DirectoryItem? GetParentDirectoryItem() { if(Info.Parent == null) { return null; } else { return GetItemByDirectoryInfo(Info.Parent); } } /// /// Gets a new directory item by combining the existing directory item with the given path fragments /// /// Base directory to append path fragments to /// The path fragments to append /// Directory item corresponding to the combined path public static DirectoryItem Combine(DirectoryItem BaseDirectory, params string[] Fragments) { return DirectoryItem.GetItemByDirectoryReference(DirectoryReference.Combine(BaseDirectory.Location, Fragments)); } /// /// Finds or creates a directory item from its location /// /// Path to the directory /// The directory item for this location public static DirectoryItem GetItemByPath(string Location) { return GetItemByDirectoryReference(new DirectoryReference(Location)); } /// /// Finds or creates a directory item from its location /// /// Path to the directory /// The directory item for this location public static DirectoryItem GetItemByDirectoryReference(DirectoryReference Location) { DirectoryItem? Result; if(!LocationToItem.TryGetValue(Location, out Result)) { DirectoryItem NewItem = new DirectoryItem(Location, new DirectoryInfo(Location.FullName)); if(LocationToItem.TryAdd(Location, NewItem)) { Result = NewItem; } else { Result = LocationToItem[Location]; } } return Result; } /// /// Finds or creates a directory item from a DirectoryInfo object /// /// Path to the directory /// The directory item for this location public static DirectoryItem GetItemByDirectoryInfo(DirectoryInfo Info) { DirectoryReference Location = new DirectoryReference(Info); DirectoryItem? Result; if(!LocationToItem.TryGetValue(Location, out Result)) { DirectoryItem NewItem = new DirectoryItem(Location, Info); if(LocationToItem.TryAdd(Location, NewItem)) { Result = NewItem; } else { Result = LocationToItem[Location]; } } return Result; } /// /// Reset the contents of the directory and allow them to be fetched again /// public void ResetCachedInfo() { Info = new DirectoryInfo(Info.FullName); Dictionary? PrevDirectories = Directories; if(PrevDirectories != null) { foreach(DirectoryItem SubDirectory in PrevDirectories.Values) { SubDirectory.ResetCachedInfo(); } Directories = null; } Dictionary? PrevFiles = Files; if(PrevFiles != null) { foreach(FileItem File in PrevFiles.Values) { File.ResetCachedInfo(); } Files = null; } } /// /// Resets all cached directory info. Significantly reduces performance; do not use unless strictly necessary. /// public static void ResetAllCachedInfo_SLOW() { foreach(DirectoryItem Item in LocationToItem.Values) { Item.Info = new DirectoryInfo(Item.Info.FullName); Item.Directories = null; Item.Files = null; } FileItem.ResetAllCachedInfo_SLOW(); } /// /// Caches the subdirectories of this directories /// public void CacheDirectories() { if(Directories == null) { Dictionary NewDirectories = new Dictionary(DirectoryReference.Comparer); if(Info.Exists) { foreach(DirectoryInfo SubDirectoryInfo in Info.EnumerateDirectories()) { if(SubDirectoryInfo.Name.Length == 1 && SubDirectoryInfo.Name[0] == '.') { continue; } else if(SubDirectoryInfo.Name.Length == 2 && SubDirectoryInfo.Name[0] == '.' && SubDirectoryInfo.Name[1] == '.') { continue; } else { NewDirectories[SubDirectoryInfo.Name] = DirectoryItem.GetItemByDirectoryInfo(SubDirectoryInfo); } } } Directories = NewDirectories; } } /// /// Enumerates all the subdirectories /// /// Sequence of subdirectory items public IEnumerable EnumerateDirectories() { CacheDirectories(); return Directories!.Values; } /// /// Attempts to get a sub-directory by name /// /// Name of the directory /// If successful receives the matching directory item with this name /// True if the file exists, false otherwise public bool TryGetDirectory(string Name, [NotNullWhen(true)] out DirectoryItem? OutDirectory) { if(Name.Length > 0 && Name[0] == '.') { if(Name.Length == 1) { OutDirectory = this; return true; } else if(Name.Length == 2 && Name[1] == '.') { OutDirectory = GetParentDirectoryItem(); return OutDirectory != null; } } CacheDirectories(); return Directories!.TryGetValue(Name, out OutDirectory); } /// /// Caches the files in this directory /// public void CacheFiles() { if(Files == null) { Dictionary NewFiles = new Dictionary(FileReference.Comparer); if(Info.Exists) { foreach(FileInfo FileInfo in Info.EnumerateFiles()) { FileItem FileItem = FileItem.GetItemByFileInfo(FileInfo); FileItem.UpdateCachedDirectory(this); NewFiles[FileInfo.Name] = FileItem; } } Files = NewFiles; } } /// /// Enumerates all the files /// /// Sequence of FileItems public IEnumerable EnumerateFiles() { CacheFiles(); return Files!.Values; } /// /// Attempts to get a file from this directory by name. Unlike creating a file item and checking whether it exists, this will /// not create a permanent FileItem object if it does not exist. /// /// Name of the file /// If successful receives the matching file item with this name /// True if the file exists, false otherwise public bool TryGetFile(string Name, [NotNullWhen(true)] out FileItem? OutFile) { CacheFiles(); return Files!.TryGetValue(Name, out OutFile); } /// /// Formats this object as a string for debugging /// /// Location of the directory public override string ToString() { return Location.FullName; } } /// /// Helper functions for serialization /// public static class DirectoryItemExtensionMethods { /// /// Read a directory item from a binary archive /// /// Reader to serialize data from /// Instance of the serialized directory item public static DirectoryItem ReadDirectoryItem(this BinaryArchiveReader Reader) { return Reader.ReadObjectReference(() => DirectoryItem.GetItemByDirectoryReference(Reader.ReadDirectoryReferenceNotNull())); } /// /// Write a directory item to a binary archive /// /// Writer to serialize data to /// Directory item to write public static void WriteDirectoryItem(this BinaryArchiveWriter Writer, DirectoryItem DirectoryItem) { Writer.WriteObjectReference(DirectoryItem, () => Writer.WriteDirectoryReference(DirectoryItem.Location)); } } }