Files
UnrealEngineUWP/Engine/Source/Programs/UnrealBuildTool/System/FileSystemReference.cs
Andrew Grant a0ef617fd2 Copying //UE4/Orion-Staging to //UE4/Main (Source: //Orion/Dev-General @ 2949393)
#lockdown Nick.Penwarden

==========================
MAJOR FEATURES + CHANGES
==========================

Change 2949393 on 2016/04/20 by Graeme.Thornton

	Orion non-pak file security.
	 - Removed security bypass code from platform pak file
	 - Added a delegate to pak file code which allows the game to decide whether a file should be allowed or not
	 - Added an orion delegate which whitelists appropriate files

	#rb robert.manuszewski
	#tests win64 client + dedicated server. golden path.

Change 2949232 on 2016/04/19 by david.nikdel

	#ROBOMERGE-AUTHOR: michael.noland
	Paragon: Added a distinct menu frame rate limit, currently set to 60 fps and not visible in settings (if the user sets a game frame rate limit of below 60, we also clamp the menu limit to that threshold, so they can go down but not up for menus)
	#jira OR-18017
	#rb marcus.wassmer
	#tests Ran paragon and switched between gameplay, menus, and replays, observing t.MaxFPS at different points

	#ROBOMERGE-SOURCE: CL 2949231 in //Orion/Main/...
	#ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 2949032 on 2016/04/19 by Zak.Middleton

	#orion - Lower default NetUpdateFrequency for minions (10->6). Avoid excessive latency for some knockback/knockup abilities that would have noticeable lag by forcing an update sooner when they are triggered.

	This should have the following effects:
	1. Reduce server CPU cost (we tick minions at the net frequency).
	2. Reduce server bandwidth
	3. Reduce client CPU cost (we move character capsules and perform overlaps when new positions are received).

	#rb Bart.Bressler, John.Pollard
	#codereview Dmitry.Rekman
	#tests MultiPIE AI lane, Replays

Change 2948966 on 2016/04/19 by Lina.Halper

	Added log (check) of the asset info for Anim Per Track contains invalid format key

	#rb: Michael.Noland
	#code review: Martin.Wilson, Laurent.Delayen, Michael.Noland
	#tests: editor/ cooked and test with AI_Tests with 10 bots.

Change 2948876 on 2016/04/19 by Michael.Noland

	PS4: Validate that the texture pool size is not set to automatic (-1, which will crash later on as an attempt to allocate too much memory)
	#rb none
	#codereview marcus.wassmer
	#tests Ran Paragon on PS4

Change 2948765 on 2016/04/19 by Daniel.Lamb

	Removed AssetImportData tag from cooked asset registry builds.
	#rb Andrew.Grant
	#test Cook orion

Change 2948691 on 2016/04/19 by Marcus.Wassmer

	Fix copytoresolvetarget ensure
	#rb none
	#test pc agora

Change 2948633 on 2016/04/19 by david.nikdel

	#ROBOMERGE-AUTHOR: jason.bestimt
	[AUTOMERGE]

	Fix copytoresolve crash and change validation to ensure.
	#test PC editor / PC golden path
	#rb none

	--------
	Integrated using branch //Orion/Main_to_//Orion/Release-Next (reversed) of change#2948169 by Marcus.Wassmer on 2016/04/19 10:50:32.

	#ROBOMERGE-SOURCE: CL 2948632 in //Orion/Main/...
	#ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 2948507 on 2016/04/19 by david.nikdel

	#ROBOMERGE-AUTHOR: andrew.grant
	Merging 2937781 (Pak signing) using //Orion/Dev-General_to_Release
	#rb none
	#tests cooked client, checked game runs

	#ROBOMERGE-SOURCE: CL 2948497 in //Orion/Release-0.24.1/... via CL 2948506
	#ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 2948431 on 2016/04/19 by Steve.Robb

	CL#s 2919775 and 2942793 integrated to prevent annotation map performance problems on shutdown and asserts in PIE.

	#codereview robert.manuszewski,bob.tellez
	#rb bob.tellez
	#tests Ran editor

Change 2948408 on 2016/04/19 by Leslie.Nivison

	Adding .tps
	#rb none
	#test none

Change 2948185 on 2016/04/19 by david.nikdel

	#ROBOMERGE-AUTHOR: chris.bunner
	Fix for HLOD visibility freeze.
	#tests Golden Path, Editor
	#rb rolando.caloca, michael.noland
	#lockdown andrew.grant
	#jira OR-19863

	#ROBOMERGE-SOURCE: CL 2948182 in //Orion/Release-0.24.1/... via CL 2948183
	#ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 2948149 on 2016/04/19 by Simon.Tovey

	Fixed crash. Collision rendering path was not dealing with mesh batch with 0 triangles where other paths do.

	#rb none
	#tests No more crash
	#codereview Marcus.Wassmer

Change 2948129 on 2016/04/19 by Lukasz.Furman

	fixed gameplay debugger getting stuck with outdated data pack on client,
	changed names of AI related debug cvars
	#rb none
	#tests game, PIE
	#codereview Mieszko.Zielinski

Change 2948027 on 2016/04/19 by david.nikdel

	#ROBOMERGE-AUTHOR: graeme.thornton
	Fix for OR-20033 - CRASH:  Client will crash with FRCPassPostProcessCircleDOFSetup

	#rb none
	#tests checked game runs without crashing

	#ROBOMERGE-SOURCE: CL 2948017 in //Orion/Main/...
	#ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 2947558 on 2016/04/18 by Matt.Kuhlenschmidt

	Fix compile error

	#rb none, #tests none

Change 2947509 on 2016/04/18 by Matt.Kuhlenschmidt

	Added more logging to track down

	https://jira.ol.epicgames.net/browse/OR-19841

	#rb none, #tests none

Change 2947412 on 2016/04/18 by Ryan.Gerleve

	Fix shadowed variable.

	#rb none
	#tests none

Change 2947377 on 2016/04/18 by Jamie.Dale

	Gather paths are now sorted by fuzzy-ness, so that more specific includes beat less specific excludes

	#rb Matt.Kuhlenschmidt
	#tests Built for Windows. Ran a gather, and confirmed that explicitly included heroes were now gathered, and that generically excluded heroes were absent from the gather.

Change 2947351 on 2016/04/18 by Ryan.Gerleve

	Allow overriding the demo.AsyncLoadWorld setting with a URL option when playing a replay.
	Store the entire URL in the demo net driver instead of just the map name, so that the options can be accessed later.

	#tests golden path, replays
	#rb john.pollard

Change 2947103 on 2016/04/18 by david.nikdel

	#ROBOMERGE-AUTHOR: jason.bestimt
	#ORION_MAIN - Merge 24.1 @ CL 2947071

	#RB:none
	#Tests:none

	#ROBOMERGE-SOURCE: CL 2947102 in //Orion/Main/...
	#ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 2947007 on 2016/04/18 by Zak.Middleton

	#ue4 - Improve linear smoothing in the presence of low net frequency updates.

	#rb Bart.Bressler
	#tests MultiPIE AI with lanes

Change 2946994 on 2016/04/18 by Mieszko.Zielinski

	Improvements to NavigationSystem's "abstract navigation data" support #UE4

	#rb Lukasz.Furman
	#test golden path

Change 2946760 on 2016/04/18 by Chris.Bunner

	Fixing up bad merge, recommit of CL 2819472 - ForceLOD now clamps to available LODs on primitive, i.e. use MinLOD rather than not drawing at all.
	#tests Editor
	#rb None

Change 2946745 on 2016/04/18 by david.nikdel

	#ROBOMERGE-AUTHOR: jason.bestimt
	#ORION_MAIN - Merge 24.1 @ CL 2946637

	#RB:none
	#Tests:none

	#ROBOMERGE-SOURCE: CL 2946656 in //Orion/Main/...
	#ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 2946645 on 2016/04/18 by Richard.Fawcett

	When promoting a buidl to staged, prevent enumeration of files already in S3

	Enumerating files in S3 is a slow process, and it turns out that simply uploading all chunks blindly is more efficient than enumerating existing chunks and selectively uploading only the new ones.

	#rb Leigh.Swift
	#tests This technique has already been used in launcher promotions for several months

Change 2946622 on 2016/04/18 by Richard.Fawcett

	By default, when enumerating chunks from a manifest file, skip checking they exist on disk at enumeration time.

	This will fail anyway further down the line if the files don't exist, but will improve speed of stage promotions by around five minutes. In practice, we have NEVER seen a job fail at this point because of the existence check.

	#rb Leigh.Swift
	#tests Ensure that output of ExtractDataFilenamesFromManifest method is identical both with and without bSkipExistsCheck specified.

Change 2945812 on 2016/04/15 by Daniel.Lamb

	Fixed error in diff cooked build commandlet.
	#rb ben.marsh
	#test Compile.

Change 2945110 on 2016/04/15 by Matt.Kuhlenschmidt

	Fix crash exporting actors with non-scene components to fbx

	#rb none, #tests full scene exporting on maps that crashed
	#codereview alexis.matte

Change 2945078 on 2016/04/15 by Simon.Tovey

	Fix for OR-19778

	When some pooled systems are reused, on init they have a non zero lod level but the emitter instances are created at LOD 0 initially.
	So the component did not think it had to update it's LOD but the emitters were not at the correct LOD.
	Have forced a LOD set on init when the component LOD is non-zero.

	#rb none
	#tests Works in editor and game.

	#codereview Olaf.Piesche

Change 2944664 on 2016/04/14 by Uriel.Doyon

	Fix to SM4 compilation issue
	#jira OR-19706
	#rb marcus.wassmer
	#tests tested editor in SM4 and SM5

Change 2944642 on 2016/04/14 by Lukasz.Furman

	changed waypoint switch conditions in meta nav paths
	#rb none
	#tests PIE
	#codereview Mieszko.Zielinski

Change 2944599 on 2016/04/14 by david.nikdel

	#ROBOMERGE-AUTHOR: andrew.grant
	Added sha1 to UnrealPak list output
	#rb none
	#tests listed content of pakfile

	#ROBOMERGE-SOURCE: CL 2944595 in //Orion/Release-0.24/... via CL 2944597 via CL 2944598
	#ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 2944441 on 2016/04/14 by Marcus.Wassmer

	Duplicate change to output shader compiler errors.
	#rb none
	#test run PC and see errors.

Change 2944437 on 2016/04/14 by John.Pollard

	Possible fix for https://jira.ol.epicgames.net/browse/OR-19614

	#rb JoshM
	#codereview Josh.Markiewicz
	#tests Golden path matchmaking

Change 2944430 on 2016/04/14 by david.nikdel

	#ROBOMERGE-AUTHOR: michael.noland
	Engine: Added support for more/fewer settings in individual categories to the editor scalability control widget
	#rb david.ratti
	#tests Tested in the editor

	#ROBOMERGE-SOURCE: CL 2944428 in //Orion/Main/...
	#ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 2944198 on 2016/04/14 by David.Ratti

	Paragon - register for slow/stun/root/silence callbacks on any tag count  change, not just add/remove. This is so the UI will update if you get another stack of a stackable slow GE.

	Ability system - unify client stack count change code path with server. Client now properly update owner ASC's tag map and broadcasts all delegates there.

	#rb dayY
	#tests pie

Change 2944124 on 2016/04/14 by Wes.Hunt

	Change the TPS redirects for DX modules to point to the proper DX redist TPS which is what packaged games will need.
	#codereview:leslie.nivison
	#rb none
	#tests ran UAT ListThirdPartySoftware <for Orion>

Change 2944107 on 2016/04/14 by Wes.Hunt

	MeshUtilities now depends on new module nvTessLib to better track the third party dependency.
	#codereview:daniel.wright
	#rb none
	#tests build OrionClient/Editor for Win64

Change 2944102 on 2016/04/14 by Wes.Hunt

	Tweak to UBT -ListBuildFolders to do a distinct in a better place to cut down on duplicate module searches.
	#tests ran the UBT command
	#rb none

Change 2943851 on 2016/04/14 by Ryan.Gerleve

	Fix the ForEachNetDriver helper function to get the world context directly off the world instead of going through the game instance. Ensures the correct net drivers will be used when there are multiple worlds but only one game instance.

	#rb john.pollard
	#tests golden path, replays, PIE

Change 2943847 on 2016/04/14 by Ryan.Gerleve

	Fixes to support client replay recording & playback in another world:
	When recording a replay, only swap actor roles if the remote role is ROLE_Authority
	When loading a replay checkpoint, call NetworkRemapPath to make sure paths have the correct name in the GuidCache

	#rb john.pollard
	#tests golden path, replays, PIE

Change 2943691 on 2016/04/14 by david.nikdel

	#ROBOMERGE-AUTHOR: jason.bestimt
	#ORION_24 - Fix for OR-19609, OR-19610, and OR-19611

	#RB:none
	#Tests:none

	#ROBOMERGE-SOURCE: CL 2943687 in //Orion/Release-0.24/... via CL 2943688
	#ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 2943508 on 2016/04/14 by Richard.Fawcett

	Automation: Add support for multipart file uploads to Amazon S3 to increase speed of large file uploads.

	#jira OPPBUILD-44
	#rb Leigh.Swift
	#tests Uploaded files to S3 using the new routines, downlaoded via AWS management console and ensured downloaded files identical to uploaded ones

Change 2943274 on 2016/04/13 by jason.bestimt

	#ORION_MAIN - Merge 24 @ CL 2943257

	#RB:none
	#Tests:none

	#ROBOMERGE-SOURCE: CL 2943271 in //Orion/Main/...
	#ROBOMERGE-BOT: ORION (Main -> Dev-General)

	#ROBOMERGE-SAYS: Beep boop! I couldn't merge this change. Please do it yourself, human.
	#CodeReview: david.nikdel, jason.bestimt

Change 2943178 on 2016/04/13 by Olaf.Piesche

	Bumping size of the particle curve texture to 512x512

	#rb martin.mittring

	#tests PC Editor, Game

Change 2943174 on 2016/04/13 by Aaron.McLeran

	OR-19392 Ensure condition failed: (*RequiresInitialization == 0) on loading into PVP match

	- Removing ensure since there is a rare edge case where it's possible for a sound looping node may get ResetChildren called twice.
	- Condition is when a child random node o fa looping node has a blank entry and results in no sound chosen in a given frame (which results in ResetChildren getting called). Later in the frame, if a sound had previously been playing with an active sound, it will have stop called on it, which will call NotifyWaveInstanceFinished and hit the ensure. Simply using the branch to check if the looping node has been initialized will work fine in this and other cases.

	#codereview Bob.Tellez
	#rb Bob.Tellez
	#tests ran orion with this change testing problematic sound cue

Change 2943042 on 2016/04/13 by Rob.Cannaday

	Fix crash in HTTP completion delegates on shutdown
	Stop ticking HTTP retry manager after FOnlineSubsystemImpl::Shutdown has been called
	#rb josh.markiewicz
	#tests shutting down multiple times

Change 2942913 on 2016/04/13 by Lukasz.Furman

	added meta navmesh paths
	#orion
	#rb Mieszko.Zielinski
	#tests PIE

Change 2942132 on 2016/04/13 by Wes.Hunt

	Enable UBT -ListBuildFolders to operate on Mac and iOS platforms without having to fully set up the remote environment.
	#codereview:leslie.nivison
	#rb peter.sauerbrei
	#tests running UBT with and without -listbuildfolders

Change 2941651 on 2016/04/12 by Jason.Bestimt

	#ORION_DG - Merge MAIN @ CL 2941645

	#RB:none
	#Tests:none

Change 2941539 on 2016/04/12 by Laurent.Delayen

	FABRIK: Normalize outgoing rotations.
	Fixes Chains Q ability crashing.

	#rb none
	#tests Chains not crashing

Change 2941469 on 2016/04/12 by Wes.Hunt

	Fix UBT -ListBuildFolders to not prep target for deployment.
	#codereview:leslie.nivison
	#rb none
	#tests tested -ListBuildFolders for Android

Change 2941434 on 2016/04/12 by Leslie.Nivison

	Adding/cleaning up .tps files
	#rb none
	#test none

Change 2941241 on 2016/04/12 by Daniel.Lamb

	Removed shadername from the shader code to fix deterministic material cooking issue.
	#jira UE-29320
	#codereview Marcus.Wassmer
	#rb Marcus.Wassmer
	#test Running editor, cooking orion.

Change 2941046 on 2016/04/12 by Laurent.Delayen

	Added safety net for non state AnimNotifies having a non-zero EndTriggerTimeOffset.
	Fixes Twinblast double shot for the left primary attack.

	#rb benn.gallagher
	#codereview lina.halper, ray.arnett, aaron.eady
	#tests twinblast's LMB

Change 2941032 on 2016/04/12 by Jason.Bestimt

	#ORION_24 - Merge MAIN @ CL 2940950

	#RB:none
	#Tests:none

[CL 2952833 by Andrew Grant in Main branch]
2016-04-22 11:21:10 -04:00

814 lines
26 KiB
C#

// 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
{
/// <summary>
/// Base class for file system objects (files or directories).
/// </summary>
[Serializable]
public abstract class FileSystemReference
{
/// <summary>
/// The path to this object. Stored as an absolute path, with O/S preferred separator characters, and no trailing slash for directories.
/// </summary>
public readonly string FullName;
/// <summary>
/// The canonical full name for this object.
/// </summary>
public readonly string CanonicalName;
/// <summary>
/// Constructs a filesystem object for the given path.
/// </summary>
public FileSystemReference(string InPath)
{
FullName = Path.GetFullPath(InPath).TrimEnd(Path.DirectorySeparatorChar);
CanonicalName = FullName.ToLowerInvariant();
}
/// <summary>
/// Constructs a reference from the given FileSystemInfo.
/// </summary>
public FileSystemReference(FileSystemInfo InInfo)
{
FullName = InInfo.FullName;
CanonicalName = FullName.ToLowerInvariant();
}
/// <summary>
/// Direct constructor for a path
/// </summary>
protected FileSystemReference(string InFullName, string InCanonicalName)
{
FullName = InFullName;
CanonicalName = InCanonicalName;
}
/// <summary>
/// Create a full path by concatenating multiple strings
/// </summary>
/// <returns></returns>
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();
}
/// <summary>
/// Checks whether this name has the given extension.
/// </summary>
/// <param name="Extension">The extension to check</param>
/// <returns>True if this name has the given extension, false otherwise</returns>
public bool HasExtension(string Extension)
{
if (Extension.Length > 0 && Extension[0] != '.')
{
return HasExtension("." + Extension);
}
else
{
return CanonicalName.EndsWith(Extension.ToLowerInvariant());
}
}
/// <summary>
/// Determines if the given object is at or under the given directory
/// </summary>
/// <param name="Directory"></param>
/// <returns></returns>
public bool IsUnderDirectory(DirectoryReference Other)
{
return CanonicalName.StartsWith(Other.CanonicalName) && (CanonicalName.Length == Other.CanonicalName.Length || CanonicalName[Other.CanonicalName.Length] == Path.DirectorySeparatorChar);
}
/// <summary>
/// Creates a relative path from the given base directory
/// </summary>
/// <param name="Directory">The directory to create a relative path from</param>
/// <returns>A relative path from the given directory</returns>
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();
}
/// <summary>
/// Returns a string representation of this filesystem object
/// </summary>
/// <returns>Full path to the object</returns>
public override string ToString()
{
return FullName;
}
}
/// <summary>
/// Representation of an absolute directory path. Allows fast hashing and comparisons.
/// </summary>
[Serializable]
public class DirectoryReference : FileSystemReference, IEquatable<DirectoryReference>
{
/// <summary>
/// Default constructor.
/// </summary>
/// <param name="InPath">Path to this directory.</param>
public DirectoryReference(string InPath)
: base(InPath)
{
}
/// <summary>
/// Construct a DirectoryReference from a DirectoryInfo object.
/// </summary>
/// <param name="InInfo">Path to this file</param>
public DirectoryReference(DirectoryInfo InInfo)
: base(InInfo)
{
}
/// <summary>
/// Constructor for creating a directory object directly from two strings.
/// </summary>
/// <param name="InFullName"></param>
/// <param name="InCanonicalName"></param>
protected DirectoryReference(string InFullName, string InCanonicalName)
: base(InFullName, InCanonicalName)
{
}
/// <summary>
/// Gets the top level directory name
/// </summary>
/// <returns>The name of the directory</returns>
public string GetDirectoryName()
{
return Path.GetFileName(FullName);
}
/// <summary>
/// Gets the directory containing this object
/// </summary>
/// <returns>A new directory object representing the directory containing this object</returns>
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));
}
}
/// <summary>
/// Gets the parent directory for a file
/// </summary>
/// <param name="File">The file to get directory for</param>
/// <returns>The full directory name containing the given file</returns>
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));
}
/// <summary>
/// Creates the directory
/// </summary>
public void CreateDirectory()
{
Directory.CreateDirectory(FullName);
}
/// <summary>
/// Checks whether the directory exists
/// </summary>
/// <returns>True if this directory exists</returns>
public bool Exists()
{
return Directory.Exists(FullName);
}
/// <summary>
/// Enumerate files from a given directory
/// </summary>
/// <returns>Sequence of file references</returns>
public IEnumerable<FileReference> EnumerateFileReferences()
{
foreach (string FileName in Directory.EnumerateFiles(FullName))
{
yield return FileReference.MakeFromNormalizedFullPath(FileName);
}
}
/// <summary>
/// Enumerate files from a given directory
/// </summary>
/// <returns>Sequence of file references</returns>
public IEnumerable<FileReference> EnumerateFileReferences(string Pattern)
{
foreach (string FileName in Directory.EnumerateFiles(FullName, Pattern))
{
yield return FileReference.MakeFromNormalizedFullPath(FileName);
}
}
/// <summary>
/// Enumerate files from a given directory
/// </summary>
/// <returns>Sequence of file references</returns>
public IEnumerable<FileReference> EnumerateFileReferences(string Pattern, SearchOption Option)
{
foreach (string FileName in Directory.EnumerateFiles(FullName, Pattern, Option))
{
yield return FileReference.MakeFromNormalizedFullPath(FileName);
}
}
/// <summary>
/// Enumerate subdirectories in a given directory
/// </summary>
/// <returns>Sequence of directory references</returns>
public IEnumerable<DirectoryReference> EnumerateDirectoryReferences()
{
foreach (string DirectoryName in Directory.EnumerateDirectories(FullName))
{
yield return DirectoryReference.MakeFromNormalizedFullPath(DirectoryName);
}
}
/// <summary>
/// Enumerate subdirectories in a given directory
/// </summary>
/// <returns>Sequence of directory references</returns>
public IEnumerable<DirectoryReference> EnumerateDirectoryReferences(string Pattern)
{
foreach (string DirectoryName in Directory.EnumerateDirectories(FullName, Pattern))
{
yield return DirectoryReference.MakeFromNormalizedFullPath(DirectoryName);
}
}
/// <summary>
/// Enumerate subdirectories in a given directory
/// </summary>
/// <returns>Sequence of directory references</returns>
public IEnumerable<DirectoryReference> EnumerateDirectoryReferences(string Pattern, SearchOption Option)
{
foreach (string DirectoryName in Directory.EnumerateDirectories(FullName, Pattern, Option))
{
yield return DirectoryReference.MakeFromNormalizedFullPath(DirectoryName);
}
}
/// <summary>
/// Determines whether this path represents a root directory in the filesystem
/// </summary>
/// <returns>True if this path is a root directory, false otherwise</returns>
public bool IsRootDirectory()
{
return CanonicalName[CanonicalName.Length - 1] == Path.DirectorySeparatorChar;
}
/// <summary>
/// Combine several fragments with a base directory, to form a new directory name
/// </summary>
/// <param name="BaseDirectory">The base directory</param>
/// <param name="Fragments">Fragments to combine with the base directory</param>
/// <returns>The new directory name</returns>
public static DirectoryReference Combine(DirectoryReference BaseDirectory, params string[] Fragments)
{
string FullName = FileSystemReference.CombineStrings(BaseDirectory, Fragments);
return new DirectoryReference(FullName, FullName.ToLowerInvariant());
}
/// <summary>
/// Compares two filesystem object names for equality. Uses the canonical name representation, not the display name representation.
/// </summary>
/// <param name="A">First object to compare.</param>
/// <param name="B">Second object to compare.</param>
/// <returns>True if the names represent the same object, false otherwise</returns>
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;
}
}
/// <summary>
/// Compares two filesystem object names for inequality. Uses the canonical name representation, not the display name representation.
/// </summary>
/// <param name="A">First object to compare.</param>
/// <param name="B">Second object to compare.</param>
/// <returns>False if the names represent the same object, true otherwise</returns>
public static bool operator !=(DirectoryReference A, DirectoryReference B)
{
return !(A == B);
}
/// <summary>
/// Compares against another object for equality.
/// </summary>
/// <param name="Obj">other instance to compare.</param>
/// <returns>True if the names represent the same object, false otherwise</returns>
public override bool Equals(object Obj)
{
return (Obj is DirectoryReference) && ((DirectoryReference)Obj) == this;
}
/// <summary>
/// Compares against another object for equality.
/// </summary>
/// <param name="Obj">other instance to compare.</param>
/// <returns>True if the names represent the same object, false otherwise</returns>
public bool Equals(DirectoryReference Obj)
{
return Obj == this;
}
/// <summary>
/// Returns a hash code for this object
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return CanonicalName.GetHashCode();
}
/// <summary>
/// Helper function to create a remote directory reference. Unlike normal DirectoryReference objects, these aren't converted to a full path in the local filesystem.
/// </summary>
/// <param name="AbsolutePath">The absolute path in the remote file system</param>
/// <returns>New directory reference</returns>
public static DirectoryReference MakeRemote(string AbsolutePath)
{
return new DirectoryReference(AbsolutePath, AbsolutePath.ToLowerInvariant());
}
/// <summary>
/// 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().
/// </summary>
/// <param name="AbsolutePath">The absolute path in the file system</param>
/// <returns>New file reference</returns>
public static DirectoryReference MakeFromNormalizedFullPath(string AbsolutePath)
{
return new DirectoryReference(AbsolutePath, AbsolutePath.ToLowerInvariant());
}
/// <summary>
/// Gets the parent directory for a file, or returns null if it's null.
/// </summary>
/// <param name="File">The file to create a directory reference for</param>
/// <returns>The directory containing the file </returns>
public static DirectoryReference FromFile(FileReference File)
{
return (File == null)? null : File.Directory;
}
}
/// <summary>
/// Representation of an absolute file path. Allows fast hashing and comparisons.
/// </summary>
[Serializable]
public class FileReference : FileSystemReference, IEquatable<FileReference>
{
/// <summary>
/// Default constructor.
/// </summary>
/// <param name="InPath">Path to this file</param>
public FileReference(string InPath)
: base(InPath)
{
}
/// <summary>
/// Construct a FileReference from a FileInfo object.
/// </summary>
/// <param name="InInfo">Path to this file</param>
public FileReference(FileInfo InInfo)
: base(InInfo)
{
}
/// <summary>
/// Default constructor.
/// </summary>
/// <param name="InPath">Path to this file</param>
protected FileReference(string InFullName, string InCanonicalName)
: base(InFullName, InCanonicalName)
{
}
/// <summary>
/// Gets the file name without path information
/// </summary>
/// <returns>A string containing the file name</returns>
public string GetFileName()
{
return Path.GetFileName(FullName);
}
/// <summary>
/// Gets the file name without path information or an extension
/// </summary>
/// <returns>A string containing the file name without an extension</returns>
public string GetFileNameWithoutExtension()
{
return Path.GetFileNameWithoutExtension(FullName);
}
/// <summary>
/// Gets the file name without path or any extensions
/// </summary>
/// <returns>A string containing the file name without an extension</returns>
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);
}
}
/// <summary>
/// Gets the extension for this filename
/// </summary>
/// <returns>A string containing the extension of this filename</returns>
public string GetExtension()
{
return Path.GetExtension(FullName);
}
/// <summary>
/// Change the file's extension to something else
/// </summary>
/// <param name="Extension">The new extension</param>
/// <returns>A FileReference with the same path and name, but with the new extension</returns>
public FileReference ChangeExtension(string Extension)
{
string NewFullName = Path.ChangeExtension(FullName, Extension);
return new FileReference(NewFullName, NewFullName.ToLowerInvariant());
}
/// <summary>
/// Gets the directory containing this file
/// </summary>
/// <returns>A new directory object representing the directory containing this object</returns>
public DirectoryReference Directory
{
get { return DirectoryReference.GetParentDirectory(this); }
}
/// <summary>
/// Determines whether the given filename exists
/// </summary>
/// <returns>True if it exists, false otherwise</returns>
public bool Exists()
{
return File.Exists(FullName);
}
/// <summary>
/// Deletes this file
/// </summary>
public void Delete()
{
File.Delete(FullName);
}
/// <summary>
/// Combine several fragments with a base directory, to form a new filename
/// </summary>
/// <param name="BaseDirectory">The base directory</param>
/// <param name="Fragments">Fragments to combine with the base directory</param>
/// <returns>The new file name</returns>
public static FileReference Combine(DirectoryReference BaseDirectory, params string[] Fragments)
{
string FullName = FileSystemReference.CombineStrings(BaseDirectory, Fragments);
return new FileReference(FullName, FullName.ToLowerInvariant());
}
/// <summary>
/// Append a string to the end of a filename
/// </summary>
/// <param name="A">The base file reference</param>
/// <param name="B">Suffix to be appended</param>
/// <returns>The new file reference</returns>
public static FileReference operator +(FileReference A, string B)
{
return new FileReference(A.FullName + B, A.CanonicalName + B.ToLowerInvariant());
}
/// <summary>
/// Compares two filesystem object names for equality. Uses the canonical name representation, not the display name representation.
/// </summary>
/// <param name="A">First object to compare.</param>
/// <param name="B">Second object to compare.</param>
/// <returns>True if the names represent the same object, false otherwise</returns>
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;
}
}
/// <summary>
/// Compares two filesystem object names for inequality. Uses the canonical name representation, not the display name representation.
/// </summary>
/// <param name="A">First object to compare.</param>
/// <param name="B">Second object to compare.</param>
/// <returns>False if the names represent the same object, true otherwise</returns>
public static bool operator !=(FileReference A, FileReference B)
{
return !(A == B);
}
/// <summary>
/// Compares against another object for equality.
/// </summary>
/// <param name="Obj">other instance to compare.</param>
/// <returns>True if the names represent the same object, false otherwise</returns>
public override bool Equals(object Obj)
{
return (Obj is FileReference) && ((FileReference)Obj) == this;
}
/// <summary>
/// Compares against another object for equality.
/// </summary>
/// <param name="Obj">other instance to compare.</param>
/// <returns>True if the names represent the same object, false otherwise</returns>
public bool Equals(FileReference Obj)
{
return Obj == this;
}
/// <summary>
/// Returns a hash code for this object
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return CanonicalName.GetHashCode();
}
/// <summary>
/// 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.
/// </summary>
/// <param name="AbsolutePath">The absolute path in the remote file system</param>
/// <returns>New file reference</returns>
public static FileReference MakeRemote(string AbsolutePath)
{
return new FileReference(AbsolutePath, AbsolutePath.ToLowerInvariant());
}
/// <summary>
/// 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().
/// </summary>
/// <param name="AbsolutePath">The absolute path in the file system</param>
/// <returns>New file reference</returns>
public static FileReference MakeFromNormalizedFullPath(string AbsolutePath)
{
return new FileReference(AbsolutePath, AbsolutePath.ToLowerInvariant());
}
}
static class FileReferenceExtensionMethods
{
/// <summary>
/// Manually serialize a file reference to a binary stream.
/// </summary>
/// <param name="Writer">Binary writer to write to</param>
public static void Write(this BinaryWriter Writer, FileReference File)
{
Writer.Write((File == null) ? String.Empty : File.FullName);
}
/// <summary>
/// Serializes a file reference, using a lookup table to avoid serializing the same name more than once.
/// </summary>
/// <param name="Writer">The writer to save this reference to</param>
/// <param name="File">A file reference to output; may be null</param>
/// <param name="FileToUniqueId">A lookup table that caches previous files that have been output, and maps them to unique id's.</param>
public static void Write(this BinaryWriter Writer, FileReference File, Dictionary<FileReference, int> 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);
}
}
/// <summary>
/// Manually deserialize a file reference from a binary stream.
/// </summary>
/// <param name="Reader">Binary reader to read from</param>
/// <returns>New FileReference object</returns>
public static FileReference ReadFileReference(this BinaryReader Reader)
{
string FullName = Reader.ReadString();
return (FullName.Length == 0) ? null : FileReference.MakeFromNormalizedFullPath(FullName);
}
/// <summary>
/// Deserializes a file reference, using a lookup table to avoid writing the same name more than once.
/// </summary>
/// <param name="Reader">The source to read from</param>
/// <param name="UniqueFiles">List of previously read file references. The index into this array is used in place of subsequent ocurrences of the file.</param>
/// <returns>The file reference that was read</returns>
public static FileReference ReadFileReference(this BinaryReader Reader, List<FileReference> 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;
}
}
}
}