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 static class Utils { static bool _caseKnown = false; static bool _caseSensitive = false; private static readonly ILog log = LogManager.GetLogger(typeof(Utils)); /// /// Remove directory wild cards from the end of a path /// /// Asset Path, including wild cards /// Asset Paths stripped of ending wild cards public static string RemoveDirectoryWildcards(string path) { if (path.EndsWith("...")) { path = path.Substring(0, path.Length - 3); if (path.EndsWith("/")) { path = path.Substring(0, path.Length - 1); } } return path; } public static string AddDirectoryWildcards(string path) { if (! path.EndsWith("...")) { return path + "/..."; } return path; } public static bool HasDirectoryWildcards(string path) { return (path.EndsWith("...") || path.EndsWith("*")); } /// /// Returns true if running on a case sensitive 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 == null) return "null"; 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", "*"); } /// /// Compare the list of submitted files to those retrieved. /// If there are unmatched files, they are reported using aErrorMessage /// /// Submitted Files /// Files returned from operation /// error message appropriate for left-overs public static void CheckForMissingFilesAndWarn(IList aSubmitted, IList aRetrieved, string aErrorMessage) { #if DEBUG //log.Debug("CheckForMissingFilesAndWarn in: " + Logger.FileSpecListToString(aSubmitted) + " out: " + Logger.FileSpecListToString(aRetrieved)); //log.Debug("ErrorMessage: " + aErrorMessage); #endif if (aSubmitted != null && aSubmitted.Count > 0) { // Make a copy of the submitted list List remainder = new List(aSubmitted.UnversionedSpecs()); // Remove special entries remainder.RemoveAll(spec => spec == null || (spec.ToString().EndsWith("..."))); //log.Debug("remainder: " + Logger.FileSpecListToString(remainder)); // Remove all the files we did get back if (aRetrieved != null) { foreach (var spec in aRetrieved.UnversionedSpecs()) { 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)); // log.Debug("\t against: " + spec.LocalPath.Path); #endif if (spec.LocalPath.Path.EndsWith(LocalPathToRelativePath(remainder[i].LocalPath.Path), !IsCaseSensitive(), null)) { //log.Debug("\t removed element : " + i.ToString()); remainder.RemoveAt(i); break; } } } } 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 = aFiles.ToFileAndMetas().ToList(); 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 = aFiles.ToFileAndMetas().ToList(); 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); } /// /// Logs a warning to the console, formatting the filename properly /// public static void LogFileWarning(string aFile, string aMsg) { Debug.LogWarning("P4Connect - " + 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 ""; } } /// /// Convert a Depot Path to an Asset Path /// Does not call back to server for a "where". Uses Config.DepotProjectRoot matching /// /// depot path /// asset path public static string DepotPathToAssetPath(string dpath) { string DepotPrefix = RemoveDirectoryWildcards(Config.DepotProjectRoot); if (dpath.StartsWith(DepotPrefix)) { return dpath.Substring(DepotPrefix.Length + 1); } log.Debug("Malformed DepotPath: " + dpath); return "Asset/" + dpath; } public static string ClientPathToAssetPath(string aClientPath) { return LocalPathToAssetPath(ClientPathToLocalPath(aClientPath)); } 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, '/'); } /// /// Checks an asset path to see if it is a directory /// Assumes paths ending with "..." are directories /// Assumes paths without extensions are directories /// /// Asset Path to test /// "true" if is directory 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; } } /// /// Test an asset path to see if it contains a non empty FileName /// /// /// public static bool IsValidFilename(string aAssetPath) { var fn = Path.GetFileNameWithoutExtension(aAssetPath); if (fn == null || fn.Length <= 0) return false; return true; } /// /// Predicate used to identify which Asset Paths which are directories /// /// Asset Path to check /// "true" if path is a directory public static bool IsDirectoryAsset(string aAssetPath) { string fullpath = System.IO.Path.Combine(Main.RootPath, aAssetPath).Replace('/', Path.DirectorySeparatorChar); return Directory.Exists(fullpath + Path.DirectorySeparatorChar); } /// /// Return the .meta path for a given asset /// /// path to Asset /// path to .meta file associated with the Asset public static string MetaFromAsset(string aAssetPath) { return aAssetPath + ".meta"; } /// /// Given a path to a .meta file, return the associated asset path /// /// Path to .meta file /// Path to asset public static string AssetFromMeta(string aMetaPath) { return aMetaPath.Substring(0, aMetaPath.Length - 5); } /// /// Convert an Asset path to a Full Path /// /// Asset Path to convert /// Full Path 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 (IsMetaFile(aName)) { 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"; } } /// /// Given a filename, compute the meta file name /// /// asset path to get meta for /// asset path to matching meta file public static string GetMetaFile(string aName) { if (IsMetaFile(aName)) return aName; return aName + ".meta"; } /// /// Given a FileState, return a string description. /// /// /// 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; } /// /// Query Unity to get the currently selected objects and convert them into Asset paths. /// /// An Enumeration of asset paths for the selected objects public static IEnumerable GetSelectedAssetPaths() { var objects = Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.Assets); foreach (var obj in objects) { string assetPath = AssetDatabase.GetAssetPath(obj); yield return assetPath; } yield break; } public static void LaunchDiffAgainstHaveRev(string aAsset) { if (System.IO.File.Exists(Config.DiffToolPathname)) { Engine.PerformConnectionOperation(con => { FileMetaData metaData = AssetStatusCache.GetCachedMetaDataAndUpdateDefaults(aAsset); BaseFileType baseFileType = metaData.Type.BaseType; if ((baseFileType == BaseFileType.Text) || (baseFileType == BaseFileType.Unicode) || (baseFileType == BaseFileType.UTF16)) { if (metaData.HaveRev != -1) { FileSpec headRevSpec = FileSpec.LocalSpec(Utils.AssetPathToLocalPath(aAsset), metaData.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 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; } public static string GetEditorAssetRelativeDirectory() { string fullPath = GetEditorAssetFullDirectory(); return FullPathToAssetPath(fullPath); } 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; } /// /// Matches up a list of meta data to the list of files it was generated from /// Uses local paths. /// Which files to find metadata for /// A list of random Metadata /// One Entry per file, contains Metadata or null /// public static void GetMatchingMetaData(List aFiles, IList aMetaData, List aOutMeta) { if (aMetaData == null || aFiles == null || aOutMeta == null) return; //Debug.Log(Logger.FileMetaDataListToString(aMetaData)); var metaDataPaths = aMetaData.ToDictionary(g => g.LocalPath.Path.ToLowerInvariant(), g => g); //Debug.Log(Logger.FileSpecListToString(aFiles)); foreach (var file in aFiles) { if (file != null) { FileMetaData meta = null; if (! metaDataPaths.TryGetValue(GetLocalPath(file).ToLowerInvariant(), out meta)) { meta = new FileMetaData(); } 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 IsMetaFile(string aPath) { return (aPath.EndsWith(".meta")); } /// /// Compare two local paths to see if one contains the other /// /// A directory Local path /// A parent directory Local path /// true or false public static bool IsDirOrValidSubDirectoryOf(string aSubPath, string aAncestorPath) { if (! IsCaseSensitive()) { aSubPath = aSubPath.ToLower(); aAncestorPath = aAncestorPath.ToLower(); } if (aSubPath.StartsWith(aAncestorPath.TrimEnd(System.IO.Path.DirectorySeparatorChar, '/'), StringComparison.InvariantCultureIgnoreCase)) return true; //Debug.Log("aSubPath: |" + aSubPath + "|"); //Debug.Log("Ancestor: |" + aAncestorPath + "|"); return false; } public static string GetLocalPath(FileSpec fs) { if (fs.LocalPath != null && fs.LocalPath.Path != null) return fs.LocalPath.Path; else if (fs.ClientPath != null && fs.ClientPath.Path != null) return ClientPathToLocalPath(fs.ClientPath.Path); Debug.Log("no local or clientpath in FileSpec"); return (""); } /// /// Returns the path to use when printing the name, or looking up status info /// Directories get EffectivePaths because they are used to associate .meta files. /// /// Asset Path /// The effective path public static string GetEffectivePath(string aAssetPath) { if (Utils.IsDirectory(aAssetPath)) { return Utils.MetaFromAsset(Utils.RemoveDirectoryWildcards(aAssetPath)); } else { return aAssetPath; } } } // Utils class /// /// Extensions class contains extension methods for use /// public static class Extensions { private static readonly ILog log = LogManager.GetLogger(typeof(Extensions)); static readonly Revision InvalidRevision = new Revision(-1); #region FileAndOp Enumerable Extensions /// /// From a Collection of operations, remove all the entries with No operation specified /// /// Collection of FileAndOperations /// Filtered Collection public static IEnumerable RemoveNoOps(this IEnumerable faos) { foreach(var fao in faos) { if (fao.FileOp != Engine.FileOperation.None) yield return fao; } yield break; } #endregion #region Generic Enumerable Extensions /// /// An Extension to split an IEnumberable into smaller pieces. /// /// /// /// /// public static IEnumerable> Split(this IEnumerable source, int len) { if (len == 0) throw new ArgumentNullException(); var enumer = source.GetEnumerator(); while (enumer.MoveNext()) { yield return Take(enumer.Current, enumer, len); } } private static IEnumerable Take(T head, IEnumerator tail, int len) { while (true) { yield return head; if (--len == 0) break; if (tail.MoveNext()) head = tail.Current; else break; } } /// /// Generic extension method that removes all null entries from a IEnumerable /// public static IEnumerable NonNullElements(this IEnumerable elements) { foreach (T fs in elements) { if (fs == null) continue; yield return fs; } yield break; } #endregion #region FileSpec Enumerable Extensions /// /// Extension method to convert a list of Filespecs into ones with valid localpaths /// /// An enumerable of /// a valid PerforceConnection /// public static IEnumerable FixLocalPaths(this IEnumerable aDepotPaths, PerforceConnection aConnection) { if (aDepotPaths == null || !aDepotPaths.Any()) { //log.Debug("no depot paths"); yield break; } var needed = aDepotPaths.UnversionedSpecs(); foreach (var entry in aDepotPaths) { if (entry.LocalPath != null) yield return entry.LocalPath; var assetPath = entry.ToAssetPath(); entry.LocalPath = new LocalPath(Utils.AssetPathToLocalPath(assetPath)); yield return entry; } //IList mapping = aConnection.P4Client.GetClientFileMappings(needed.ToArray()); //if (mapping == null || mapping.Count == 0) //{ // //log.Debug("no mappings"); // yield break; //} ////log.Debug("ClientFileMappings: " + Logger.FileSpecListToString(aDepotPaths.ToList())); //foreach (var entry in mapping) //{ // //log.Debug("fix: " + entry.ToStringNullSafe()); // FileSpec val = entry; // if (entry.LocalPath == null && entry.ClientPath != null) // { // val = FileSpec.LocalSpec(Utils.ClientPathToLocalPath(entry.ClientPath.Path)); // } // yield return val; //} //yield break; } /// /// Extension to convert a FileSpec into an assetpath /// /// /// public static string ToAssetPath(this FileSpec spec) { if (HasLocalPath(spec)) return Utils.LocalPathToAssetPath(spec.LocalPath.Path); if (HasClientPath(spec)) return Utils.ClientPathToAssetPath(spec.ClientPath.Path); // Must only have a depot path return Utils.DepotPathToAssetPath(spec.DepotPath.Path); } /// /// Extension for FileSpec which allows the creation of FileAndMeta collections from FileSpec collections /// /// Filespecs to process /// FileAndMetas (matched pairs when possible) public static IEnumerable ToFileAndMetas(this IEnumerable specs) { if (specs == null) yield break; FamManager fm = new FamManager(); foreach (FileSpec spec in specs) fm.Add(spec); foreach (var val in fm.Values) yield return val; } /// /// Extension for FileSpec, return only entries with valid versions /// /// /// public static IEnumerable ValidSpecs(this IEnumerable specs) { return specs.Where(s => !Version.Equals(s.Version, InvalidRevision)); } /// /// From a list of filespecs, just select the ones with NonLocal Specs /// /// /// public static IEnumerable NonLocalSpecs(this IEnumerable specs) { return specs.Where(s => !s.HasLocalPath()); } public static IEnumerable JustLocalSpecs(this IEnumerable specs) { return specs.Where(s => s.HasLocalPath()); } /// /// Create a list of Unversioned Filespecs from an existing list /// /// FileSpecs to copy /// FileSpecs with Versions stripped public static IEnumerable UnversionedSpecs(this IEnumerable specs) { foreach (var fs in specs) { yield return fs.StripVersion(); } yield break; } public static IEnumerable ToLocalPaths(this IEnumerable specs) { foreach (var fs in specs) { yield return fs.ToLocalPath(); } yield break; } public static IEnumerable ToAssetPaths(this IEnumerable specs) { foreach (var fs in specs) { yield return fs.ToAssetPath(); } yield break; } /// /// Create a HashSet from an Enumeration of Asset Paths. /// /// Asset Paths /// Hashset containing Asset Paths public static HashSet ToHashSet(this IEnumerable files) { HashSet contents = new HashSet(files); return contents; } #endregion #region PathString Enumerable Extensions /// /// Scan a collection of asset paths, Identify directories, add Perforce wildcard "/..." /// /// paths to scan /// paths with directories containing wildcards public static IEnumerable AddDirectoryWildcards(this IEnumerable paths) { foreach( string path in paths) { if (Utils.IsDirectory(path) && (! path.EndsWith("..."))) { yield return path + "/..."; } else { yield return path; } } } /// /// Given an enumeration of asset paths, look for meta files which represent directories /// Add the directory path (with wildcards) to the enumeration /// /// asset paths /// asset paths with additional directory entries public static IEnumerable AddDirectoriesFromMetas(this IEnumerable paths) { var assets = new HashSet(paths); // Look for Meta files associated with directories, if found, also add the directory with Perforce wildcards // to the asset list foreach (var f in paths) { if (Utils.IsMetaFile(f)) { var rname = Utils.AssetFromMeta(f); if (Utils.IsDirectory(rname)) { yield return Utils.AddDirectoryWildcards(rname); } } yield return f; } } /// /// Strip Directory paths from a path collection /// /// path collection /// Filtered path collection public static IEnumerable StripDirectories(this IEnumerable paths) { foreach (string path in paths) { if (! Utils.IsDirectory(path)) { yield return path; } } } /// /// Return only Directory paths from an asset path collection /// /// path collection /// Just the directory paths public static IEnumerable JustDirectories(this IEnumerable paths) { foreach (string path in paths) { if (Utils.IsDirectory(path)) { yield return path; } } } /// /// Remove paths containing Directory Wildcards from an AssetPath enumeration /// /// asset Paths Enumeration /// asset Paths containing no wildcards public static IEnumerable RemoveWildCards(this IEnumerable paths) { foreach (var path in paths) { if (!Utils.HasDirectoryWildcards(path)) yield return path; } } public static IEnumerable AssetToLocalPaths(this IEnumerable paths) { foreach (var path in paths) { yield return Utils.AssetPathToLocalPath(path); } } public static IEnumerable LocalToAssetPaths(this IEnumerable paths) { foreach (var path in paths) { yield return Utils.LocalPathToAssetPath(path); } } public static IEnumerable EffectivePaths(this IEnumerable paths) { foreach (string path in paths) { yield return Utils.GetEffectivePath(path); } } public static IEnumerable ToDepotSpecs(this IEnumerable assetPaths) { string depotRoot = Utils.RemoveDirectoryWildcards(Config.DepotProjectRoot) + "/"; foreach (string asset in assetPaths) { yield return FileSpec.DepotSpec(depotRoot + asset); } } public static IEnumerable ToLocalSpecs(this IEnumerable assetPaths) { string localRoot = Utils.RemoveDirectoryWildcards(Config.LocalProjectRoot) + System.IO.Path.DirectorySeparatorChar; foreach (string asset in assetPaths) { yield return FileSpec.LocalSpec(localRoot + asset); } } /// /// Given a Collection of Local Paths, Explode the list to include all children in the output Collection /// Only return the files with full paths, not directories /// /// /// public static IEnumerable ExplodeLocalPaths(this IEnumerable paths) { foreach (string path in paths) { if (Utils.IsDirectory(path)) { var npath = Utils.RemoveDirectoryWildcards(path); // Get The Folder Contents var files = Directory.GetFiles(npath, "*.*", SearchOption.TopDirectoryOnly); foreach (string file in files) { yield return file; } // Recurse into subdirectories var dfiles = ExplodeLocalPaths(Directory.GetDirectories(npath)); foreach (string dfile in dfiles) { yield return dfile; } } else { yield return path; } } yield break; } /// /// Takes a list of asset paths. /// For directories in the path, recurse inside /// /// list of all paths to examine. /// list of all "leaf nodes" in the passed in tree. public static IEnumerable ExplodeAssetPaths(this IEnumerable paths) { // Debug.Log("ExplodeAssetPaths " + Logger.StringArrayToString(paths.ToArray())); foreach (string path in paths.AssetToLocalPaths().ExplodeLocalPaths().LocalToAssetPaths()) { //Debug.Log("Result Asset: " + path); yield return path; } } #endregion #region FileAndMeta Enumerable Extensions /// /// Create an enumerable of FileSpec from an enumerable of FileAndMeta /// /// FileAndMeta enumerable /// FileSpec enumerable public static IEnumerable ToFileSpecs(this IEnumerable fams) { foreach (var fam in fams) { if (fam.File != null) { yield return fam.File; } if (fam.Meta != null) { yield return fam.Meta; } } } #endregion #region FileMetaData Extensions public static string ToAssetPath(this FileMetaData fmd) { return Utils.ClientPathToAssetPath(fmd.ClientPath.Path); } public static IEnumerable ToAssetPaths(this IEnumerable fmds) { foreach (FileMetaData fmd in fmds) { yield return ToAssetPath(fmd); } } #endregion #region FamManager /// /// Class used by ToFilesAndMetas to collect files into pairs (asset and meta) with appropriate types. /// Results are returned as a FileAndMeta collection /// class FamManager { private static readonly ILog log = LogManager.GetLogger(typeof(FamManager)); Dictionary famDictionary; public FamManager() { famDictionary = new Dictionary(); } /// /// Add an asset path to the Fam Manager /// /// public void Add(FileSpec fspec) { //log.Debug("Add: " + path); fspec = fspec.StripVersion(); // remove any version information from FileSpec string path = fspec.ToString(); if (Utils.IsMetaFile(path)) { // It's a meta file, is there an asset in the list as well? string assetpath = Utils.AssetFromMeta(path); FileAndMeta existingFam; if (famDictionary.TryGetValue(assetpath, out existingFam)) { existingFam.Meta = fspec; famDictionary[assetpath] = existingFam; // update existing entry } else { // Add a new entry to the famDictionary existingFam.Meta = fspec; existingFam.File = null; famDictionary[assetpath] = existingFam; // Add new entry to famDictionary } } else { string metapath = Utils.MetaFromAsset(path); FileAndMeta existingFam; // It's not a meta file, is there a meta as well? if (famDictionary.TryGetValue(path, out existingFam)) { existingFam.File = fspec; famDictionary[path] = existingFam; // update existing entry } else { FileAndMeta fam = new FileAndMeta(); fam.File = fspec; fam.Meta = null; famDictionary[path] = fam; // Add entry to famDictionary } } } /// /// Return all famDictionary elements /// public ICollection Values { get { return famDictionary.Values; } } } #endregion #region FileSpec Extensions /// /// Extension for FileSpec to return asset path /// /// FileSpec to convert to asset path /// If provided, this routine could query the server to determine asset path from depot path /// Asset Path, relative to project public static string ToAssetPath(this FileSpec spec, PerforceConnection con = null) { string path = ""; if (spec.LocalPath != null) path = Utils.LocalPathToAssetPath(spec.LocalPath.Path); else if (spec.ClientPath != null) path = Utils.LocalPathToAssetPath(Utils.ClientPathToLocalPath(spec.ClientPath.Path)); else if (spec.DepotPath != null && con != null) path = Utils.LocalPathToAssetPath(Utils.DepotPathToLocalPath(con, spec.DepotPath)); #if DEBUG if (path.Length == 0) Debug.Log("ToAssetPath came up EMPTY!" + spec.ToStringNullSafe()); #endif return path; } /// /// Return true if a LocalPath exists and is not empty /// /// /// bool public static bool HasLocalPath(this FileSpec spec) { if (spec == null || spec.LocalPath == null || spec.LocalPath.Path == null || spec.LocalPath.Path.Length == 0) return false; return true; } /// /// Return true if a ClientPath exists and is not empty /// /// /// bool public static bool HasClientPath(this FileSpec spec) { if (spec == null || spec.ClientPath == null || spec.ClientPath.Path == null || spec.ClientPath.Path.Length == 0) return false; return true; } /// /// Extension to convert FileSpec into a localpath /// /// /// public static string ToLocalPath(this FileSpec spec) { if (spec.LocalPath != null) return(spec.LocalPath.Path); else if (spec.ClientPath != null) return(Utils.ClientPathToLocalPath(spec.ClientPath.Path)); #if DEBUG log.Debug("Missing Local Path"); #endif return(""); } #endregion #region AssetStatus Extensions /// /// Extension which takes a set of asset paths and makes sure the AssetStatusCache is updated with "Real" values. /// AssetStatus Updates run SYNCHRONOUSLY /// /// asset paths to synchronize /// The same asset paths as originally input public static IEnumerable InitializeAssetStatuses(this IEnumerable aAssetPaths) { List defaults = new List(); foreach (var path in aAssetPaths) { if (! AssetStatusCache.GetCachedAssetStatus(path).IsInitialized()) defaults.Add(path); } if (defaults.Count > 0) { Engine.PerformConnectionOperation(con => { AssetStatusCache.GetAssetMetaData(con, defaults); // Run an fstat command synchronously, when complete it will update entry. }, false); } return aAssetPaths; } /// /// Extension which takes a set of asset paths and makes sure that each Uninitialized AssetStatusCache is updated with "Real" values. /// AssetStatuses are update ASYNCHRONOUSLY /// /// asset paths to synchronize /// The same asset paths passed in public static IEnumerable PrepareAssetStatuses(this IEnumerable aAssetPaths) { List defaults = new List(); foreach (var path in aAssetPaths) { if (!AssetStatusCache.GetCachedAssetStatus(path).IsInitialized()) defaults.Add(path); } if (defaults.Count > 0) { AssetStatusCache.GetFstatsFromServer(defaults); } return aAssetPaths; } /// /// Extension which returns a collection of AssetStatuses from a collection of assetPaths. /// /// paths of assets to get status /// Ienumerable of AssetStatuses public static IEnumerable GetCachedAssetStatuses(this IEnumerable aAssetPaths) { foreach (var path in aAssetPaths) { yield return AssetStatusCache.GetCachedAssetStatus(path); } } /// /// Extension which returns a collection of FileMetaDatas from a collection of assetPaths. /// /// paths of assets /// Ienumerable of FileMetaData public static IEnumerable GetCachedAssetMetaDatas(this IEnumerable aAssetPaths) { foreach (var path in aAssetPaths) { yield return AssetStatusCache.GetCachedMetaData(path); } } #endregion } }