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; namespace P4Connect { /// /// Utility methods for P4Connect /// public class Utils { /// /// 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 = System.IO.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 (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) { if (spec.LocalPath.Path.EndsWith(remainder[i].LocalPath.Path)) { 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(); Debug.LogWarning("P4Connect - " + errorMsg); EditorUtility.DisplayDialog("P4Connect - Error", errorMsg, "Ok"); } } } /// /// 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); } /// /// 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 AssetPathToLocalPath(string aAssetPath) { return System.IO.Path.Combine(Config.ProjectRoot, aAssetPath).Replace('/', System.IO.Path.DirectorySeparatorChar); } public static string LocalPathToAssetPath(string aLocalPath) { if (Config.ProjectRoot != "" && aLocalPath.StartsWith(Config.ProjectRoot)) { var cleanedRoot = Config.ProjectRoot.TrimEnd(System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar); if (aLocalPath.Length > cleanedRoot.Length + 1) { return aLocalPath.Substring(cleanedRoot.Length + 1).Replace(System.IO.Path.DirectorySeparatorChar, '/'); } else { return aLocalPath.Replace(System.IO.Path.DirectorySeparatorChar, '/'); } } else { return aLocalPath.Replace(System.IO.Path.DirectorySeparatorChar, '/'); } } public static string LocalPathToClientPath(string aLocalPath) { string ClientPrefix = "//" + Config.Workspace; return ClientPrefix + "/" + aLocalPath.Replace(System.IO.Path.DirectorySeparatorChar, '/'); } public static bool IsDirectory(string aAssetPath) { if (aAssetPath.EndsWith("...")) return true; else { string fullPath = LocalPathToFullPath(aAssetPath); if (System.IO.Directory.Exists(fullPath + System.IO.Path.DirectorySeparatorChar)) return true; else if (System.IO.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 System.IO.Path.Combine(Main.RootPath, aAssetPath.Replace('/', System.IO.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 string GetDLLFullDirectory() { return System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); } 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) { 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); } return fileAndMetas; } /// /// Matches up a list of meta data to the list of files it was generated from /// 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) { metaDataPaths[meta.LocalPath.Path.ToLowerInvariant()] = meta; } } } foreach (var file in aFiles) { if (file != null) { string lcPath = Utils.LocalPathToFullPath(file.LocalPath.Path).ToLowerInvariant(); FileMetaData meta = null; metaDataPaths.TryGetValue(lcPath, out meta); aOutMeta.Add(meta); } else { 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; } } }