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)); /// /// Remove directory wild cards from the end of a path /// /// /// public static string RemoveDirectoryWildcards(string path) { if (path.EndsWith("...")) { return path.Substring(0, path.Length - 4); } return path; } public static string AddDirectoryWildcards(string path) { if (! path.EndsWith("...")) { return path + "/..."; } return path; } /// /// 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 == 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 ""; } } 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 (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 /// /// file to get meta for /// meta filename 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; } 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; } /// /// 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(IEnumerable paths) { foreach(string path in paths) { if (System.IO.Directory.Exists(path)) { // Get The Folder Contents var files = System.IO.Directory.GetFiles(path, "*.*", System.IO.SearchOption.TopDirectoryOnly); foreach (string file in files) { yield return file; } var dfiles = ExplodeLocalPaths(System.IO.Directory.GetDirectories(path)); foreach (string dfile in dfiles) { yield return dfile; } } else { yield return path; } } yield break; } 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 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; } /// /// 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")); } 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; } 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 (""); } } // Utils class /// /// Extensions class contains extension methods for use /// public static class Extensions { private static readonly ILog log = LogManager.GetLogger(typeof(Extensions)); /// /// From a Collection of operations, remove all the entries with No operation specified /// /// Collection of FileAndOperations /// Filtered Collection public static IEnumerable NoNoOps(this IEnumerable faos) { foreach(var fao in faos) { if (fao.FileOp != Engine.FileOperation.None) yield return fao; } yield break; } /// /// Extension method to convert a list of Filespecs into ones with valid localpaths /// /// /// /// 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(); 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; } /// /// Scan a collection of asset paths, Identify directoryies, 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; } } yield break; } /// /// 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; } } yield break; } /// /// 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; } } yield break; } /// /// 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) { FamManager fm = new FamManager(); foreach(FileSpec spec in specs) fm.Add(spec); foreach (var val in fm.Values) yield return (val); yield break; } #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 existing_fam = new FileAndMeta(); if (famDictionary.TryGetValue(assetpath, out existing_fam)) { existing_fam.Meta = fspec; famDictionary[assetpath] = existing_fam; // update existing entry } else { // Add a new entry to the famDictionary existing_fam.Meta = fspec; existing_fam.File = null; famDictionary[assetpath] = existing_fam; // Add new entry to famDictionary } } else { string metapath = Utils.MetaFromAsset(path); FileAndMeta existing_fam = new FileAndMeta(); // It's not a meta file, is there a meta as well? if (famDictionary.TryGetValue(path, out existing_fam)) { existing_fam.File = fspec; famDictionary[path] = existing_fam; // 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 { //foreach (var entry in famDictionary.Values) //{ // log.Debug("Dict entry: " + entry.ToString()); //} // log.Debug("fams: " + Logger.FileAndMetaListToString(famDictionary.Values.ToList())); return famDictionary.Values; } } } #endregion /// /// 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; } /// /// 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; } /// /// 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(""); } static Revision invalid_revision = new Revision(-1); /// /// Extension for FileSpec, return only entries with valid versions /// /// /// public static IEnumerable ValidSpecs(this IEnumerable specs) { return specs.Where( s => ! Version.Equals(s.Version, invalid_revision)); } /// /// 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 AssetToLocalPaths(this IEnumerable paths) { foreach(var path in paths) { yield return Utils.AssetPathToLocalPath(path); } yield break; } public static IEnumerable LocalToAssetPaths(this IEnumerable paths) { foreach (var path in paths) { yield return Utils.LocalPathToAssetPath(path); } yield break; } } }