// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using EpicGames.Core;
namespace HordeServer.Utilities
{
///
/// Stores a mapping from one set of paths to another
///
public class ViewMap
{
///
/// List of entries making up the view
///
public List Entries { get; }
///
/// Default constructor
///
public ViewMap()
{
Entries = new List();
}
///
/// Constructor
///
///
public ViewMap(ViewMap other)
{
Entries = new List(other.Entries);
}
///
/// Determines if a file is included in the view
///
/// The file to test
/// The comparison type
/// True if the file is included in the view
public bool MatchFile(Utf8String file, Utf8StringComparer comparison)
{
bool included = false;
foreach (ViewMapEntry entry in Entries)
{
if (entry.MatchFile(file, comparison))
{
included = entry.Include;
}
}
return included;
}
///
/// Attempts to convert a source file to its target path
///
///
/// The comparison type
///
///
public bool TryMapFile(Utf8String sourceFile, Utf8StringComparer comparison, out Utf8String targetFile)
{
ViewMapEntry? mapEntry = null;
foreach (ViewMapEntry entry in Entries)
{
if (entry.MatchFile(sourceFile, comparison))
{
mapEntry = entry;
}
}
if (mapEntry != null && mapEntry.Include)
{
targetFile = mapEntry.MapFile(sourceFile);
return true;
}
else
{
targetFile = Utf8String.Empty;
return false;
}
}
///
/// Gets the root paths from the view entries
///
///
public List GetRootPaths(Utf8StringComparer comparison)
{
List rootPaths = new List();
foreach (ViewMapEntry entry in Entries)
{
if (entry.Include)
{
int lastSlashIdx = entry.SourcePrefix.LastIndexOf('/');
Utf8String rootPath = entry.SourcePrefix.Slice(0, lastSlashIdx + 1);
for (int idx = 0; ; idx++)
{
if (idx == rootPaths.Count)
{
rootPaths.Add(rootPath);
break;
}
else if (rootPaths[idx].StartsWith(rootPath, comparison))
{
rootPaths[idx] = rootPath;
break;
}
else if (rootPath.StartsWith(rootPaths[idx], comparison))
{
break;
}
}
}
}
return rootPaths;
}
}
///
/// Entry within a ViewMap
///
public class ViewMapEntry
{
///
/// Whether to include files matching this pattern
///
public bool Include { get; }
///
/// The wildcard string - either '*' or '...'
///
public Utf8String Wildcard { get; }
///
/// The source part of the pattern before the wildcard
///
public Utf8String SourcePrefix { get; }
///
/// The source part of the pattern after the wildcard. Perforce does not permit a slash to be in this part of the mapping.
///
public Utf8String SourceSuffix { get; }
///
/// The target mapping for the pattern before the wildcard
///
public Utf8String TargetPrefix { get; }
///
/// The target mapping for the pattern after the wildcard
///
public Utf8String TargetSuffix { get; }
///
/// The full source pattern
///
public Utf8String Source => new Utf8String($"{SourcePrefix}{Wildcard}{SourceSuffix}");
///
/// The full target pattern
///
public Utf8String Target => new Utf8String($"{TargetPrefix}{Wildcard}{TargetSuffix}");
///
/// Tests if the entry has a file wildcard ('*')
///
/// True if the entry has a file wildcard
public bool IsFileWildcard() => Wildcard.Length == 1;
///
/// Tests if the entry has a path wildcard ('...')
///
/// True if the entry has a path wildcard
public bool IsPathWildcard() => Wildcard.Length == 3;
///
/// Constructor
///
///
public ViewMapEntry(ViewMapEntry other)
: this(other.Include, other.Wildcard, other.SourcePrefix, other.SourceSuffix, other.TargetPrefix, other.TargetSuffix)
{
}
///
/// Constructor
///
///
///
///
public ViewMapEntry(bool include, string source, string target)
{
Include = include;
Match match = Regex.Match(source, @"^(.*)(\*|\.\.\.|%%1)(.*)$");
if (match.Success)
{
string wildcardStr = match.Groups[2].Value;
SourcePrefix = new Utf8String(match.Groups[1].Value);
SourceSuffix = new Utf8String(match.Groups[3].Value);
Wildcard = new Utf8String(match.Groups[2].Value);
int otherIdx = target.IndexOf(wildcardStr, StringComparison.Ordinal);
TargetPrefix = new Utf8String(target.Substring(0, otherIdx));
TargetSuffix = new Utf8String(target.Substring(otherIdx + Wildcard.Length));
if (wildcardStr.Equals("%%1", StringComparison.Ordinal))
{
Wildcard = new Utf8String("*");
}
}
else
{
SourcePrefix = new Utf8String(source);
SourceSuffix = Utf8String.Empty;
TargetPrefix = new Utf8String(target);
TargetSuffix = Utf8String.Empty;
}
}
///
/// Constructor
///
///
///
///
///
///
///
public ViewMapEntry(bool include, Utf8String wildcard, Utf8String sourcePrefix, Utf8String sourceSuffix, Utf8String targetPrefix, Utf8String targetSuffix)
{
Include = include;
Wildcard = wildcard;
SourcePrefix = sourcePrefix;
SourceSuffix = sourceSuffix;
TargetPrefix = targetPrefix;
TargetSuffix = targetSuffix;
}
///
/// Maps a file to the target path
///
///
///
public Utf8String MapFile(Utf8String sourceFile)
{
int count = sourceFile.Length - SourceSuffix.Length - SourcePrefix.Length;
return TargetPrefix + sourceFile.Slice(SourcePrefix.Length, count) + TargetSuffix;
}
///
/// Determine if a file matches the current entry
///
/// Path to the file
/// The comparison type
/// True if the path matches the entry
public bool MatchFile(Utf8String path, Utf8StringComparer comparison)
{
if (Wildcard.Length == 0)
{
return comparison.Compare(path, SourcePrefix) == 0;
}
else
{
if (!path.StartsWith(SourcePrefix, comparison) || !path.EndsWith(SourceSuffix, comparison))
{
return false;
}
if (IsFileWildcard() && path.Slice(SourcePrefix.Length, path.Length - SourceSuffix.Length - SourcePrefix.Length).IndexOf('/') != -1)
{
return false;
}
return true;
}
}
///
public override string ToString()
{
StringBuilder builder = new StringBuilder();
if (!Include)
{
builder.Append('-');
}
builder.Append(CultureInfo.InvariantCulture, $"{SourcePrefix}{Wildcard}{SourceSuffix} {TargetPrefix}{Wildcard}{TargetSuffix}");
return builder.ToString();
}
}
}