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