using UnityEditor;
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Perforce.P4;
using System.Runtime.InteropServices;
using System.Reflection;
using System.IO;
using log4net;
namespace P4Connect
{
///
/// Utility methods for P4Connect
///
public class Utils
{
static bool caseKnown = false;
static bool caseSensitive = false;
private static readonly ILog log = LogManager.GetLogger(typeof(Utils));
///
/// Returns true if running on a case insensitive file system.
///
public static bool IsCaseSensitive()
{
// Return cached results
if (caseKnown)
return caseSensitive;
// The assets directory should always exist in a project
string path = Main.DataPath;
bool u = System.IO.Directory.Exists(path.ToUpper());
bool l = System.IO.Directory.Exists(path.ToLower());
if (u ^ l)
return true; // default to true for a new project, but don't cache the result.
if (u && l){
caseSensitive = false;
} else {
caseSensitive = true;
}
caseKnown = true;
return caseSensitive;
}
public static bool IsRunningUnity5()
{
if (Application.unityVersion.Trim()[0] == '5')
return true;
return false;
}
///
/// Retrieves a displayable filename from a perforce file spec
///
public static string GetFilename(FileAndMeta aFileAndMeta)
{
return GetFilename(aFileAndMeta, Config.ShowPaths);
}
///
/// Retrieves a displayable filename from a perforce file spec
///
public static string GetFilename(FileAndMeta aFileAndMeta, bool aShowPath)
{
// Base version uses whatever prefs the user set
if (aFileAndMeta.File != null)
return GetFilename(aFileAndMeta.File, aShowPath);
else if (aFileAndMeta.Meta != null)
return GetFilename(aFileAndMeta.Meta, aShowPath);
else
return "";
}
///
/// Retrieves a displayable filename from a perforce file spec
///
public static string GetFilename(Perforce.P4.FileSpec aFile)
{
// Base version uses whatever prefs the user set
return GetFilename(aFile, Config.ShowPaths);
}
///
/// Retrieves a displayable filename from a perforce file spec
///
public static string GetFilename(Perforce.P4.FileSpec aFile, bool aShowPath)
{
string filename = "";
if (aFile.ClientPath != null)
filename = aFile.ClientPath.Path;
else if (aFile.DepotPath != null)
filename = aFile.DepotPath.Path;
else
filename = aFile.LocalPath.Path;
if (!filename.EndsWith("...") && !aShowPath)
{
// parse out the file name
filename = Path.GetFileName(filename);
}
return UnescapeFilename(filename);
}
///
/// Separates the files based on their extension
///
public static void FilterTextAndBinary(List aFiles, List aOutText, List aOutBinary, List aOutUnknown)
{
foreach (var file in aFiles)
{
switch (FilterTextAndBinary(file))
{
case FilterTextAndBinaryResult.Binary:
aOutBinary.Add(file);
break;
case FilterTextAndBinaryResult.Text:
aOutText.Add(file);
break;
case FilterTextAndBinaryResult.Unknown:
aOutUnknown.Add(file);
break;
}
}
}
public enum FilterTextAndBinaryResult
{
Text,
Binary,
Unknown
}
///
/// Separates the files based on their extension
///
public static FilterTextAndBinaryResult FilterTextAndBinary(FileSpec aFile)
{
bool defaultToBinary = false;
if (EditorSettings.serializationMode == SerializationMode.ForceBinary || EditorSettings.serializationMode == SerializationMode.Mixed)
{
defaultToBinary = true;
}
string extension = System.IO.Path.GetExtension(GetFilename(aFile, false)).ToLowerInvariant();
// Certain files are forced as text!
if (extension == ".meta" ||
extension == ".txt" ||
extension == ".html" ||
extension == ".htm" ||
extension == ".xml" ||
extension == ".bytes" ||
extension == ".json" ||
extension == ".csv" ||
extension == ".yaml" ||
extension == ".fnt" ||
extension == ".js" ||
extension == ".cs" ||
extension == ".sln" ||
extension == ".csproj" ||
extension == ".compute" ||
extension == ".unityproj")
{
return FilterTextAndBinaryResult.Text;
}
// Others depend on the serialization setting
else if (extension == ".scene" ||
extension == ".prefab" ||
extension == ".mat" ||
extension == ".cubemap" ||
extension == ".flare" ||
extension == ".rendertexture" ||
extension == ".controller" ||
extension == ".anim" ||
extension == ".overridecontroller" ||
extension == ".mask" ||
extension == ".physicmaterial" ||
extension == ".physicmaterial2d" ||
extension == ".guiskin" ||
extension == ".fontsettings")
{
if (defaultToBinary)
{
return FilterTextAndBinaryResult.Binary;
}
else
{
return FilterTextAndBinaryResult.Text;
}
}
// And the rest, we leave up to perforce to determine.
else
{
return FilterTextAndBinaryResult.Unknown;
}
}
///
/// Escape filenames for perforce
///
public static string EscapeFilename(string aFilename)
{
return aFilename.Replace("%", "%25").Replace("@", "%40").Replace("#", "%23").Replace("*", "%2A");
}
///
/// Unescape filenames for perforce
///
public static string UnescapeFilename(string aFilename)
{
return aFilename.Replace("%25", "%").Replace("%40", "@").Replace("%23", "#").Replace("%2A", "*");
}
public static void CheckForMissingFilesAndWarn(IList aSubmitted, IList aRetrieved, string aErrorMessage)
{
#if DEBUG
log.Debug("CheckForMissingFilesAndWarn");
log.Debug(aSubmitted);
log.Debug(aRetrieved);
log.Debug("ErrorMessage: " + aErrorMessage);
#endif
if (aSubmitted != null && aSubmitted.Count > 0)
{
// Make a copy of the submitted list
List remainder = new List(aSubmitted);
// Remove special entries
remainder.RemoveAll(spec => spec == null || (spec.ClientPath != null && spec.ClientPath.Path != null && spec.ClientPath.Path.EndsWith("...")));
// Remove all the files we did get back
if (aRetrieved != null)
{
foreach (var spec in aRetrieved)
{
for (int i = 0; i < remainder.Count; ++i)
{
// Submitted is Asset Path
// Retrieved is Depot Paths
#if DEBUG
log.Debug("\ttry this: " + LocalPathToRelativePath(remainder[i].LocalPath.Path));
#endif
if (spec.LocalPath.Path.EndsWith(LocalPathToRelativePath(remainder[i].LocalPath.Path), !IsCaseSensitive(), null))
{
remainder.RemoveAt(i);
}
}
}
}
if (remainder.Count > 0)
{
StringBuilder builder = new StringBuilder();
builder.AppendLine(aErrorMessage);
foreach (var spec in remainder)
{
if (spec != null)
{
builder.AppendLine("\t" + GetFilename(spec, true));
}
}
string errorMsg = builder.ToString();
#if DEBUG
log.Debug("\tP4Connect - " + errorMsg);
#endif
EditorUtility.DisplayDialog("P4Connect - Error", errorMsg, "Ok");
}
}
}
#region LogFiles
///
/// Logs a message to the console, formatting the filename properly
///
public static void LogFile(FileAndMeta aFileAndMeta, string aMsg)
{
if (aFileAndMeta.File != null)
{
string filename = GetFilename(aFileAndMeta.File);
if (aFileAndMeta.Meta != null)
filename += " (and meta)";
Debug.Log("P4Connect - " + string.Format(aMsg, filename));
}
else
{
if (aFileAndMeta.Meta != null)
{
string filename = GetFilename(aFileAndMeta.Meta);
Debug.Log("P4Connect - " + string.Format(aMsg, filename));
}
else
{
Debug.LogError("null file AND meta passed in to LogFile()");
}
}
}
///
/// Logs a message to the console, formatting the filename properly
///
public static void LogFile(Perforce.P4.FileSpec aFile, string aMsg)
{
LogFile(new FileAndMeta(aFile, null), aMsg);
}
///
/// Logs a message to the console, formatting the filename properly
///
public static void LogFiles(IList aFiles, string aMsg)
{
if (aFiles != null)
{
List fileAndMetas = GetFileAndMetas(aFiles);
LogFiles(fileAndMetas, aMsg);
}
}
///
/// Logs a message to the console, formatting the filename properly
///
public static void LogFiles(IList aFilesAndMetas, string aMsg)
{
if (aFilesAndMetas != null && aFilesAndMetas.Count > 0)
{
if (aFilesAndMetas.Count == 1)
{
LogFile(aFilesAndMetas[0], aMsg);
}
else
{
StringBuilder builder = new StringBuilder();
builder.Append(aFilesAndMetas.Count.ToString());
builder.Append(" assets.\n");
foreach (var fileAndMeta in aFilesAndMetas)
{
if (fileAndMeta.File != null)
{
string filename = GetFilename(fileAndMeta.File, true);
if (fileAndMeta.Meta != null)
filename += " (and meta)";
builder.Append(filename);
builder.Append("\n");
}
else
{
if (fileAndMeta.Meta != null)
{
string filename = GetFilename(fileAndMeta.Meta, true);
builder.Append(filename);
builder.Append("\n");
}
else
{
Debug.LogError("null file AND meta passed in to LogFile()");
}
}
}
builder.Remove(builder.Length - 1, 1);
Debug.Log("P4Connect - " + string.Format(aMsg, builder.ToString()));
}
}
}
///
/// Logs a message to the console, formatting the filename properly
///
public static void LogFile(FileAndMeta aFileAndMeta, FileAndMeta aMoveToFileAndMeta, string aMsg, bool aShowPath)
{
if (aFileAndMeta.File != null && aMoveToFileAndMeta.File != null)
{
string filename = GetFilename(aFileAndMeta.File, aShowPath);
string moveToFilename = GetFilename(aMoveToFileAndMeta.File, aShowPath);
if (aFileAndMeta.Meta != null)
filename += " (and meta)";
Debug.Log("P4Connect - " + string.Format(aMsg, filename, moveToFilename));
}
else
{
if (aFileAndMeta.Meta != null && aMoveToFileAndMeta.Meta != null)
{
string filename = GetFilename(aFileAndMeta.Meta, aShowPath);
string moveToFilename = GetFilename(aMoveToFileAndMeta.Meta, aShowPath);
Debug.Log("P4Connect - " + string.Format(aMsg, filename, moveToFilename));
}
else
{
Debug.LogError("null file AND meta passed in to LogFile()");
}
}
}
///
/// Logs a message to the console, formatting the filename properly
///
public static void LogFiles(IList aFilesAndMetas, IList aMoveToFilesAndMetas, string aMsg)
{
if (aFilesAndMetas != null && aMoveToFilesAndMetas != null && aFilesAndMetas.Count > 0 && aMoveToFilesAndMetas.Count > 0)
{
if (aFilesAndMetas.Count == 1 && aMoveToFilesAndMetas.Count == 1)
{
LogFile(aFilesAndMetas[0], aMoveToFilesAndMetas[0], aMsg);
}
else
{
StringBuilder builderFrom = new StringBuilder();
foreach (var fileAndMeta in aFilesAndMetas)
{
if (fileAndMeta.File != null)
{
string filename = GetFilename(fileAndMeta.File, true);
if (fileAndMeta.Meta != null)
filename += " (and meta)";
builderFrom.Append(filename);
builderFrom.Append("\n");
}
else
{
if (fileAndMeta.Meta != null)
{
string filename = GetFilename(fileAndMeta.Meta, true);
builderFrom.Append(filename);
builderFrom.Append("\n");
}
else
{
Debug.LogError("null file AND meta passed in to LogFile()");
}
}
}
builderFrom.Remove(builderFrom.Length - 1, 1);
Debug.Log("P4Connect - " + string.Format(aMsg, aFilesAndMetas.Count.ToString(), "a new location") + "\n" + builderFrom.ToString());
}
}
}
///
/// Logs a message to the console, formatting the filename properly
///
public static void LogFiles(IList aFiles, IList aMoveToFilesAndMetas, string aMsg)
{
if (aFiles != null && aMoveToFilesAndMetas != null)
{
List fileAndMetas = GetFileAndMetas(aFiles);
LogFiles(fileAndMetas, aMoveToFilesAndMetas, aMsg);
}
}
///
/// Logs a message to the console, formatting the filename properly
///
public static void LogFile(FileAndMeta aFileAndMeta, FileAndMeta aMoveToFileAndMeta, string aMsg)
{
LogFile(aFileAndMeta, aMoveToFileAndMeta, aMsg, Config.ShowPaths);
}
///
/// Logs a message to the console, formatting the filename properly
///
public static void LogFile(Perforce.P4.FileSpec aFile, Perforce.P4.FileSpec aMoveToFile, string aMsg, bool aShowPath)
{
LogFile(new FileAndMeta(aFile, null), new FileAndMeta(aMoveToFile, null), aMsg, aShowPath);
}
///
/// Logs a message to the console, formatting the filename properly
///
public static void LogFile(Perforce.P4.FileSpec aFile, Perforce.P4.FileSpec aMoveToFile, string aMsg)
{
LogFile(aFile, aMoveToFile, aMsg, Config.ShowPaths);
}
///
/// Logs a warning to the console, formatting the filename properly
///
public static void LogFileWarning(Perforce.P4.FileSpec aFile, string aMsg)
{
Debug.LogWarning("P4Connect - " + GetFilename(aFile) + aMsg);
}
#endregion
///
/// Checks whether the filepath can be used by perforce
///
public static bool IsFilePathValid(string aFilePath)
{
bool ret = true;
char[] forbiddenChars = new char[] { '@', '#', '*', '%' };
for (int i = 0; ret && i < forbiddenChars.Length; ++i)
{
if (aFilePath.Contains(forbiddenChars[i]))
{
ret = false;
}
}
return ret;
}
public static string ClientPathToLocalPath(string aClientPath)
{
string ClientPrefix = "//" + Config.Workspace;
return aClientPath.Substring(ClientPrefix.Length + 1).Replace('/', System.IO.Path.DirectorySeparatorChar);
}
public static string ClientPathToFullPath(string aClientPath)
{
return LocalPathToFullPath(ClientPathToLocalPath(aClientPath));
}
public static string DepotPathToLocalPath(PerforceConnection aConnection, FileSpec aDepotPath)
{
IList mapping = aConnection.P4Client.GetClientFileMappings(null, aDepotPath);
if (mapping != null && mapping.Count > 0)
{
return ClientPathToLocalPath(mapping[0].ClientPath.Path);
}
else
{
return "";
}
}
public static string DepotPathToFullPath(PerforceConnection aConnection, FileSpec aDepotPath)
{
IList mapping = aConnection.P4Client.GetClientFileMappings(null, aDepotPath);
if (mapping != null && mapping.Count > 0)
{
return ClientPathToFullPath(mapping[0].ClientPath.Path);
}
else
{
return "";
}
}
public static string LocalPathToRelativePath(string aLocalPath)
{
string result = aLocalPath;
string project = Main.RootPath;
if (aLocalPath.StartsWith(project, !IsCaseSensitive(), null))
{
if (aLocalPath.Length > project.Length)
{
result = aLocalPath.Substring(project.Length);
}
}
Debug.Log("LocalPathToRelativePath returns " + result);
return result;
}
public static string AssetPathToLocalPath(string aAssetPath)
{
return System.IO.Path.Combine(Main.RootPath, aAssetPath).Replace('/', Path.DirectorySeparatorChar);
}
public static string LocalPathToAssetPath(string aLocalPath)
{
if (aLocalPath.StartsWith(Main.RootPath, !IsCaseSensitive(), null))
{
var cleanedRoot = Main.RootPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
if (aLocalPath.Length > cleanedRoot.Length + 1)
{
return aLocalPath.Substring(cleanedRoot.Length + 1).Replace(Path.DirectorySeparatorChar, '/');
}
else
{
return aLocalPath.Replace(Path.DirectorySeparatorChar, '/');
}
}
else
{
return aLocalPath.Replace(Path.DirectorySeparatorChar, '/');
}
}
public static string LocalPathToClientPath(string aLocalPath)
{
string ClientPrefix = "//" + Config.Workspace;
return ClientPrefix + "/" + aLocalPath.Replace(Path.DirectorySeparatorChar, '/');
}
public static bool IsDirectory(string aAssetPath)
{
if (aAssetPath.EndsWith("..."))
return true;
else
{
string fullPath = LocalPathToFullPath(aAssetPath);
if (Directory.Exists(fullPath + Path.DirectorySeparatorChar))
return true;
else if (Path.GetExtension(aAssetPath) == string.Empty)
return true;
else
return false;
}
}
public static string MetaFromAsset(string aAssetPath)
{
return aAssetPath + ".meta";
}
public static string AssetFromMeta(string aMetaPath)
{
return aMetaPath.Substring(0, aMetaPath.Length - 5);
}
public static string AssetPathToFullPath(string aAssetPath)
{
return Path.Combine(Main.RootPath, aAssetPath.Replace('/', Path.DirectorySeparatorChar));
}
public static string LocalPathToFullPath(string aLocalPath)
{
return AssetPathToFullPath(LocalPathToAssetPath(aLocalPath));
}
public static string FullPathToAssetPath(string aFullPath)
{
return aFullPath.Substring(Main.RootPath.Length).Replace(System.IO.Path.DirectorySeparatorChar, '/');
}
public static void GetFileAndMeta(string aName, out string aFileName, out string aMetaName)
{
// Is this a .meta file? if so we need to fetch the original file name from it
if (aName.EndsWith(".meta"))
{
aMetaName = Utils.AssetPathToLocalPath(aName);
aFileName = aMetaName.Substring(0, aMetaName.Length - 5);
}
// If it's not a meta file, generate the meta file name from it
else
{
aFileName = Utils.AssetPathToLocalPath(aName);
aMetaName = aFileName + ".meta";
}
}
public static string GetFileStateString(FileState aState)
{
string ret = "";
switch (aState)
{
case FileState.None:
ret = "";
break;
case FileState.InDepot:
ret = "None";
break;
case FileState.MarkedForEdit:
ret = "Edit";
break;
case FileState.MarkedForAdd:
ret = "Add";
break;
case FileState.MarkedForDelete:
ret = "Delete";
break;
case FileState.MarkedForAddMove:
ret = "Move/Add";
break;
case FileState.MarkedForDeleteMove:
ret = "Move/Delete";
break;
}
return ret;
}
public static string GetStorageTypeString(StorageType aType)
{
string ret = "Other";
switch (aType)
{
case StorageType.Text:
ret = "Text";
break;
case StorageType.Binary:
ret = "Binary";
break;
}
return ret;
}
public static List GetDeepSelectedEffectivePaths()
{
var objects = Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.Assets);
List assets = new List();
foreach (var obj in objects)
{
string assetPath = AssetDatabase.GetAssetPath(obj);
assets.Add(assetPath);
}
return assets;
}
public static void LaunchDiffAgainstHaveRev(string aAsset)
{
if (System.IO.File.Exists(Config.DiffToolPathname))
{
Engine.PerformConnectionOperation(con =>
{
FileSpec localSpec = FileSpec.LocalSpec(Utils.AssetPathToLocalPath(aAsset));
IList metaDataList = Engine.GetFileMetaData(con, null, localSpec);
if (metaDataList != null && metaDataList.Count == 1)
{
BaseFileType baseFileType = metaDataList[0].Type.BaseType;
if ((baseFileType == BaseFileType.Text) || (baseFileType == BaseFileType.Unicode) || (baseFileType == BaseFileType.UTF16))
{
if (metaDataList[0].HaveRev != -1)
{
FileSpec headRevSpec = FileSpec.LocalSpec(Utils.AssetPathToLocalPath(aAsset), metaDataList[0].HaveRev);
IList contents = con.P4Depot.GetFileContents(null, headRevSpec);
if (contents != null && contents.Count == 2)
{
// Create a temp file ad dump the content in there
string haveRevPathname = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetFileName(headRevSpec.ToString()));
System.IO.StreamWriter tempFile = System.IO.File.CreateText(haveRevPathname);
tempFile.Write(contents[1]);
tempFile.Close();
// Fetch the full pathname of the original file
string localPathname = Utils.AssetPathToFullPath(aAsset);
// Now run the diff tool
string arguments = haveRevPathname + " " + localPathname;
System.Diagnostics.Process.Start(Config.DiffToolPathname, arguments);
}
}
else
{
EditorUtility.DisplayDialog("File not Synced", "You have never synced this file before and cannot compare it against your HAVE revision", "Ok");
}
}
}
});
}
else
{
EditorUtility.DisplayDialog("No Diff Utility Set", "You must point P4Connect to your favorite Diff Utility in Edit->Perforce Settings to use this feature.", "Ok");
}
}
public static bool MakeWriteable(string filename)
{
FileInfo fi = new FileInfo(filename);
if (fi.Exists)
{
if (fi.IsReadOnly)
{
Debug.Log("MakeWriteable: " + filename);
fi.IsReadOnly = false; // Make the file writeable anyway to avoid problems.
return true;
}
}
return false;
}
public static string GetBridgeDirectory()
{
return System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) +
System.IO.Path.DirectorySeparatorChar + (IntPtr.Size == 4 ? "x86" : "x86_64" ) + System.IO.Path.DirectorySeparatorChar;
}
public static string GetEditorAssetFullDirectory()
{
return System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + System.IO.Path.DirectorySeparatorChar;
}
static string GetFileSpecPath(FileSpec aSpec)
{
string filePath = "";
if (aSpec.LocalPath != null)
filePath = aSpec.LocalPath.Path;
else if (aSpec.ClientPath != null)
filePath = aSpec.ClientPath.Path;
else if (aSpec.DepotPath != null)
filePath = aSpec.DepotPath.Path;
return filePath;
}
///
/// Attempts to group files with their metas from a list of file specs
///
public static List GetFileAndMetas(IList aFiles)
{
#if DEBUG
// log.DebugFormat("files: {0}", Logger.FileSpecListToString(aFiles));
#endif
List files = new List(aFiles);
List fileAndMetas = new List();
// Parse the list, seeing if files have metas and such
while (files.Count > 0)
{
string filePath = GetFileSpecPath(files[0]);
FileAndMeta assetAndMeta = new FileAndMeta();
if (filePath.EndsWith(".meta"))
{
// It's a meta file, is there an asset in the list as well?
assetAndMeta.Meta = files[0];
string assetPath = Utils.AssetFromMeta(filePath);
int assetIndex = files.FindIndex(fs => String.Compare(assetPath, GetFileSpecPath(fs), StringComparison.InvariantCultureIgnoreCase) == 0);
if (assetIndex != -1)
{
assetAndMeta.File = files[assetIndex];
files.RemoveAt(assetIndex);
}
}
else
{
// It's not a meta file, is there a meta as well?
assetAndMeta.File = files[0];
string metaPath = Utils.MetaFromAsset(filePath);
int metaIndex = files.FindIndex(fs => String.Compare(metaPath, GetFileSpecPath(fs), StringComparison.InvariantCultureIgnoreCase) == 0);
if (metaIndex != -1)
{
assetAndMeta.Meta = files[metaIndex];
files.RemoveAt(metaIndex);
}
}
// Finally, add the asset and meta to the list
fileAndMetas.Add(assetAndMeta);
// No matter what, remove the file
files.RemoveAt(0);
}
#if DEBUG
// log.DebugFormat("returns: {0}", Logger.FileAndMetaListToString(fileAndMetas));
#endif
return fileAndMetas;
}
///
/// Matches up a list of meta data to the list of files it was generated from
/// Uses full local paths.
///
public static void GetMatchingMetaData(List aFiles, IList aMetaData, List aOutMeta)
{
Dictionary metaDataPaths = new Dictionary();
if (aMetaData != null)
{
foreach (var meta in aMetaData)
{
if (meta != null)
{
//log.Debug("MetaData path: " + meta.LocalPath.Path.ToLowerInvariant());
metaDataPaths[meta.LocalPath.Path.ToLowerInvariant()] = meta;
}
}
}
foreach (var file in aFiles)
{
if (file != null)
{
string lcPath = Utils.LocalPathToFullPath(file.LocalPath.Path).ToLowerInvariant();
//log.Debug("FileData path: " + lcPath);
FileMetaData meta = null;
metaDataPaths.TryGetValue(lcPath, out meta);
aOutMeta.Add(meta);
}
else
{
//log.Debug("FileData Null!");
aOutMeta.Add(null);
}
}
}
public static bool IsSolutionProjectFile(string aPath)
{
return aPath.EndsWith(".csproj") || aPath.EndsWith(".sln");
}
public static bool IsDirOrValidSubDirectoryOf(string aSubPath, string aAncestorPath)
{
System.IO.DirectoryInfo subDir = new System.IO.DirectoryInfo(aSubPath.TrimEnd(System.IO.Path.DirectorySeparatorChar, '/'));
System.IO.DirectoryInfo ancestorDir = new System.IO.DirectoryInfo(aAncestorPath.TrimEnd(System.IO.Path.DirectorySeparatorChar, '/'));
bool isParent = false;
while (subDir != null)
{
if (string.Compare(subDir.FullName, ancestorDir.FullName, StringComparison.InvariantCultureIgnoreCase) == 0)
{
isParent = true;
break;
}
else subDir = subDir.Parent;
}
return isParent;
}
}
}