using UnityEditor; using UnityEngine; using System; using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using Perforce.P4; using System.Linq; using System.Reflection; using System.Threading; using System.Text; using log4net; namespace P4Connect { public enum StorageType { Text = 0, Binary, Other } /// /// These are the only states that P4Connect currently recognizes /// public enum FileState { None = 0, InDepot, InDepotDeleted, MarkedForEdit, MarkedForAdd, MarkedForDelete, MarkedForAddMove, MarkedForDeleteMove, IsDirectory, } /// /// Lock state of a file /// Note that a locked file can still be modified, just not submitted /// public enum LockState { None = 0, OurLock, TheirLock, } public enum DepotState { None = 0, Deleted, } public enum RevisionState { None = 0, HasLatest, OutOfDate, } public enum ResolvedState { None = 0, NeedsResolve, } // The type of operation the user is attempting, there are only a few recognized operations public enum AssetOperation { None = 0, Add, Remove, Checkout, Move, Revert, RevertIfUnchanged, GetLatest, ForceGetLatest, Lock, Unlock, } /// /// Since Unity has .meta files for 'almost' every asset file, files and metas sort of go in pairs /// BUT because they don't always, we need to keep track of what we're dealing with. /// public enum FileAndMetaType { None = 0, FileOnly, MetaOnly, FileAndMeta, } /// /// A file and its associated .meta file (either can be null) /// public struct FileAndMeta { public FileSpec File; public FileSpec Meta; public FileAndMeta(FileSpec aFile, FileSpec aMeta) { File = aFile; Meta = aMeta; } public override string ToString() { return ("file: " + File.ToStringNullSafe() + " meta: " + Meta.ToStringNullSafe()); } } /// /// An Asset and its associated .meta file (either can be null) /// Uses Asset Path strings instead of fileSpecs. /// public struct AssetAndMeta { public string File; public string Meta; public AssetAndMeta(string aFile, string aMeta) { File = aFile; Meta = aMeta; } public FileAndMeta ToFileAndMeta() { FileSpec fsFile = (File == null) ? null : FileSpec.LocalSpec(Utils.AssetPathToLocalPath(File)); FileSpec fsMeta = (Meta == null) ? null : FileSpec.LocalSpec(Utils.AssetPathToLocalPath(Meta)); return new FileAndMeta(fsFile,fsMeta); } public override string ToString() { return ("file: " + File.ToStringNullSafe() + " meta: " + Meta.ToStringNullSafe()); } } /// /// This class is the meat of P4Connect. It grabs lists of files and talks to the server to get stuff done /// public partial class Engine { public delegate void OnOperationPerformedDelegate(PerforceConnection aConnection, List aFileAndMetaList); public static event OnOperationPerformedDelegate OnOperationPerformed; private static readonly ILog log = LogManager.GetLogger(typeof(Engine)); // The types of operations which can be performed on a Perforce file // There are more than asset operations because the current state of the file matters. public enum FileOperation { None = 0, Add, RevertAndAddNoOverwrite, Checkout, Delete, Revert, RevertIfUnchanged, RevertAndDelete, RevertAndCheckout, RevertAndCheckoutNoOverwrite, Move, RevertAndMove, RevertAndMoveNoOverwrite, MoveToNewLocation, GetLatest, ForceGetLatest, RevertAndGetLatest, Lock, Unlock, } /// /// We try to treat assets and .meta files in pair, but they may not always be, /// and furthermore, the operations needed on each one may not be the same. /// public struct FilesAndOp { public FileSpec File; public FileSpec Meta; public FileSpec MoveToFile; public FileSpec MoveToMeta; public FileOperation FileOp; public FilesAndOp(string file, string meta, string movefile, string movemeta, FileOperation fop) { File = Meta = MoveToFile = MoveToMeta = null; if (file != null) File = FileSpec.LocalSpec(Utils.AssetPathToLocalPath(file)); if (meta != null) Meta = FileSpec.LocalSpec(Utils.AssetPathToLocalPath(meta)); if (movefile != null) MoveToFile = FileSpec.LocalSpec(Utils.AssetPathToLocalPath(movefile)); if (movemeta != null) MoveToMeta = FileSpec.LocalSpec(Utils.AssetPathToLocalPath(movemeta)); FileOp = fop; } } /// /// Stores all our potential perforce operations and for each, a list of files to perform that operation on /// class FileListOperations { private static readonly ILog log = LogManager.GetLogger(typeof(FileListOperations)); /// /// Stores a list of Perforce files (FileSpec) on which to perform one operation /// struct FileListOperation { public int FileCount { get { return _Files.Count; } } public string Description { get { return _Description; } } // The list of files to perform the P4 operation on List _Files; List _MoveToFiles; // The description of the task string _Description; // The delegate that performs the operation (quicker to do it this way than to use a class hierarchy). Func, IList, IList> _Operation; static List _EmptyList; static FileListOperation() { _EmptyList = new List(); } /// /// Initializing constructor /// /// /// public FileListOperation(string aDescription, Func, IList, IList> aOperation) { _Files = new List(); _MoveToFiles = new List(); _Description = aDescription; _Operation = aOperation; } /// /// Add a file to the list for this operation /// public void Add(FileSpec aFile, FileSpec aMoveToFile, FileSpec aMeta, FileSpec aMoveToMeta) { _Files.Add(new FileAndMeta(aFile, aMeta)); _MoveToFiles.Add(new FileAndMeta(aMoveToFile, aMoveToMeta)); } /// /// Runs the perforce operation on the files collected so far. /// public IList Run(PerforceConnection aConnection) { IList ret = null; if (_Files.Any()) { ret = _Operation(aConnection, _Files, _MoveToFiles); _Files.Clear(); } else { ret = _EmptyList; } return ret; } } // A map of operations and list of files to perform that operation on Dictionary _FileOperations; /// /// Initializing constructor /// public FileListOperations() { _FileOperations = new Dictionary(); _FileOperations.Add(FileOperation.RevertAndDelete, new FileListOperation("Deleting Files", Operations.RevertAndDelete)); _FileOperations.Add(FileOperation.Delete, new FileListOperation("Deleting Files", Operations.Delete)); _FileOperations.Add(FileOperation.Revert, new FileListOperation("Reverting Files", Operations.Revert)); _FileOperations.Add(FileOperation.RevertIfUnchanged, new FileListOperation("Reverting Unchanged Files", Operations.RevertIfUnchanged)); _FileOperations.Add(FileOperation.RevertAndCheckout, new FileListOperation("Restoring Files", Operations.RevertAndCheckout)); _FileOperations.Add(FileOperation.RevertAndCheckoutNoOverwrite, new FileListOperation("Checking out Files", Operations.RevertAndCheckoutNoOverwrite)); _FileOperations.Add(FileOperation.Add, new FileListOperation("Adding Files", Operations.Add)); _FileOperations.Add(FileOperation.RevertAndAddNoOverwrite, new FileListOperation("Moving Files", Operations.RevertAndCheckoutNoOverwrite)); _FileOperations.Add(FileOperation.Checkout, new FileListOperation("Checking out Files", Operations.Checkout)); _FileOperations.Add(FileOperation.Move, new FileListOperation("Moving Files", Operations.Move)); _FileOperations.Add(FileOperation.RevertAndMove, new FileListOperation("Moving Files", Operations.RevertAndMove)); _FileOperations.Add(FileOperation.RevertAndMoveNoOverwrite, new FileListOperation("Moving Files", Operations.RevertAndMoveNoOverwrite)); _FileOperations.Add(FileOperation.MoveToNewLocation, new FileListOperation("Moving Files", Operations.MoveToNewLocation)); _FileOperations.Add(FileOperation.GetLatest, new FileListOperation("Syncing Files", Operations.GetLatest)); _FileOperations.Add(FileOperation.ForceGetLatest, new FileListOperation("Syncing Files", Operations.ForceGetLatest)); _FileOperations.Add(FileOperation.RevertAndGetLatest, new FileListOperation("Syncing Files", Operations.RevertAndGetLatest)); _FileOperations.Add(FileOperation.Lock, new FileListOperation("Locking Files", Operations.Lock)); _FileOperations.Add(FileOperation.Unlock, new FileListOperation("Unlocking Files", Operations.Unlock)); } /// /// Add a file to the list for this operation /// public void Add(FilesAndOp aFileAndOp) { if (aFileAndOp.FileOp != FileOperation.None) { FileListOperation op = _FileOperations[aFileAndOp.FileOp]; op.Add(aFileAndOp.File, aFileAndOp.MoveToFile, aFileAndOp.Meta, aFileAndOp.MoveToMeta); } } /// /// Runs the perforce operation on the files collected so far. /// public List Run(PerforceConnection aConnection) { // Count the files int totalFiles = 0; foreach (var op in _FileOperations.Values) { totalFiles += op.FileCount; } // Perform all operations int currentCount = 0; List allSpecs = new List(); foreach (var op in _FileOperations.Values) { if (op.FileCount > 2) { EditorUtility.DisplayProgressBar("Hold on", "P4Connect - " + op.Description, (float)currentCount / (float)totalFiles); } currentCount += op.FileCount; var opRes = op.Run(aConnection); if (opRes != null) { allSpecs.AddRange(opRes); } } if (totalFiles > 2) { EditorUtility.ClearProgressBar(); } return allSpecs; } } static List EmptyFileAndMeta; /// /// Initialize the P4Connect Engine /// public static void Initialize() { EmptyFileAndMeta = new List(); } static bool check_local_ignore(string ignore_line, string path) { // string msg = "check_local_ignore: " + ignore_line + " == " + path; //Debug.Log(msg); bool caseSensitive = Utils.IsCaseSensitive(); int ignore_ll = ignore_line.Length; if (ignore_ll == 0) { return false; } int path_ll = path.Length; if (ignore_ll == path_ll) { if (0 == String.Compare(ignore_line, path, caseSensitive)) { // Debug.Log("Ignore Line Match Ignored: " + path); return true; } } else if (ignore_ll < path_ll) // could be a subdirectory { if (ignore_line[ignore_ll - 1] == '/') // ignore line ends with slash (match all children) { if (0 == String.Compare(ignore_line, 0, path, 0, ignore_ll, caseSensitive)) { // The path is a child of the ignore line //Debug.Log("Child Ignored: " + path); return true; } } } return false; } static bool is_ignored(string path, PerforceConnection aConnection) { string[] dels = new string[] { "\n", "\r" }; path = path.Trim(); // Debug.Log("is_ignored: " + path); // Check the additional ignore list: if (! String.IsNullOrEmpty(Config.IgnoreLines)) { foreach (var ipath in Config.IgnoreLines.Split(dels, StringSplitOptions.RemoveEmptyEntries)) { if (check_local_ignore(ipath.Trim(), path)) return true; } } // Check if in P4IGNORE if (aConnection.P4Connection.IsFileIgnored(path)) { //Debug.Log("P4IGNORE Ignored: " + path); return true; } return false; } public static string[] StripIgnore(string[] files, PerforceConnection aConnection) { string[] result = files.Where(path => !is_ignored(path, aConnection)).ToArray(); //log.DebugFormat("StripIgnore {0} returns {1}", Logger.StringArrayToString(files), Logger.StringArrayToString(result)); return(result); } /// /// This method is called by Unity when assets are created BY Unity itself /// Note: This is not an override because Unity calls it through Invoke() /// public static List CreateAsset(string arPath) { string[] filesToAdd = new string[] { arPath }; return CreateAssets(filesToAdd); } /// /// This method is called by Unity when assets are created BY Unity itself /// Note: This is not an override because Unity calls it through Invoke() /// public static List CreateAssets(string[] arPaths) { // Creation of assets doesn't need to happen right away return PerformOperation(arPaths, null, AssetOperation.Add); } /// /// This method is called by Unity when assets are deleted /// public static List DeleteAsset(string arPath) { // Deletion of assets doesn't need to happen right away var paths = new String[] { arPath }; return DeleteAssets(paths); } /// /// This method is called by Unity when assets are deleted /// public static List DeleteAssets(string[] arPath) { // Deletion of assets doesn't need to happen right away return PerformOperation(arPath.Where(p => p.Length > 0).ToArray(), null, AssetOperation.Remove); } /// /// Called to simply mark a file as modified /// public static List CheckoutAsset(string arPath) { // Checking out assets does need to happen right away string[] filesToCheckout = new string[] { arPath }; return CheckoutAssets(filesToCheckout); } /// /// This method is called by Unity when assets are moved /// public static List MoveAssets(string[] arPath, string[] arMoveToPath) { // Deletion of assets doesn't need to happen right away return PerformOperation(arPath, arMoveToPath, AssetOperation.Move); } /// /// /// public static List MoveAsset(string arPath, string arMoveToPath) { // Checking out assets does need to happen right away string[] filesToMove = new string[] { arPath }; string[] filesToMoveTo = new string[] { arMoveToPath }; return MoveAssets(filesToMove, filesToMoveTo); } /// /// This method is called by Unity when assets are deleted /// public static List RevertAssets(string[] arPath, bool aForce) { // Deletion of assets doesn't need to happen right away if (aForce) return PerformOperation(arPath, null, AssetOperation.Revert); else return PerformOperation(arPath, null, AssetOperation.RevertIfUnchanged); } /// /// Called to simply revert a modified file /// public static List RevertAsset(string arPath, bool aForce) { // Checking out assets does need to happen right away string[] filesToRevert = new string[] { arPath }; return RevertAssets(filesToRevert, aForce); } /// /// Called to simply mark a file as modified /// public static List CheckoutAssets(string[] arPaths) { // Checking out assets does need to happen right away return PerformOperation(arPaths, null, AssetOperation.Checkout); } /// /// Called to lock files /// public static List LockAssets(string[] arPaths) { // Checking out assets does need to happen right away return PerformOperation(arPaths, null, AssetOperation.Lock); } /// /// Called to unlock files /// public static List UnlockAssets(string[] arPaths) { // Checking out assets does need to happen right away return PerformOperation(arPaths, null, AssetOperation.Unlock); } /// /// This method is called when the user wants to sync files /// public static List GetLatestAssets(string[] arPath, bool aForce) { if (aForce) return PerformOperation(arPath, null, AssetOperation.ForceGetLatest); else return PerformOperation(arPath, null, AssetOperation.GetLatest); } /// /// This method is called when the user wants to sync files /// public static List GetLatestAsset(string arPath, bool aForce) { // Checking out assets does need to happen right away string[] filesToGetLatest = new string[] { arPath }; return GetLatestAssets(filesToGetLatest, aForce); } /// /// Returns the lock state of the passed in file /// public static LockState GetLockState(string arPath, PerforceConnection aConnection) { if (Utils.IsFilePathValid(arPath)) return GetLockState(FileSpec.LocalSpec(Utils.AssetPathToLocalPath(arPath)), aConnection); else return LockState.None; } /// /// Returns the lock state of the passed in file /// public static LockState GetLockState(FileSpec aFile, PerforceConnection aConnection) { IList dataList = GetFileMetaData(aConnection, null, aFile); return GetLockState(dataList); } /// /// Returns the lock state of the passed in file /// public static LockState GetLockState(IList aMeta) { LockState retState = LockState.None; if (aMeta != null) { foreach (var data in aMeta) { if (data.OurLock) retState = LockState.OurLock; else if (data.OtherLock) retState = LockState.TheirLock; } } return retState; } /// /// Returns the lock state of the passed in file /// public static LockState GetLockState(FileMetaData aMeta) { //log.Debug("aMeta: " + Logger.ToStringNullSafe(aMeta)); LockState retState = LockState.None; if (aMeta != null) { if (aMeta.OurLock) retState = LockState.OurLock; else if (aMeta.OtherLock) retState = LockState.TheirLock; } return retState; } /// /// Returns the state of the passed in file /// public static FileState GetFileState(string arPath, PerforceConnection aConnection) { if (Utils.IsFilePathValid(arPath)) return GetFileState(FileSpec.LocalSpec(Utils.AssetPathToLocalPath(arPath)), aConnection); else return FileState.None; } /// /// Returns the state of the passed in file /// public static FileState GetFileState(FileSpec aFile, PerforceConnection aConnection) { FileMetaData meta; IList dataList = GetFileMetaData(aConnection, null, aFile); if (dataList.Count > 0) { meta = dataList[0]; } else { meta = new FileMetaData(); } return GetOpenAction(meta); } /// /// Returns the state of the passed in file /// //public static FileState GetFileState(IList aMetaData) //{ // FileState retState = FileState.None; // if (aMetaData != null) // { // foreach (var data in aMetaData) // { // retState = ParseFileAction(data.Action); // } // } // return retState; //} /// /// Returns the "open action" from the metadata. /// public static FileState GetOpenAction(FileMetaData aMetaData) { // Debug.Log("GetOpenAction: " + aMetaData.Action.ToString() + " returns: " + ParseFileAction(aMetaData.Action)); return ParseFileAction(aMetaData.Action); } public static FileState GetHeadAction(FileMetaData aMetaData) { //Debug.Log("GetHeadAction: " + aMetaData.HeadAction.ToString() + " returns: " + ParseFileAction(aMetaData.HeadAction)); return ParseFileAction(aMetaData.HeadAction); } /// /// Returns the state of the passed in file on the server (if checked out by someone else for instance) /// public static FileState GetServerFileState(string arPath, PerforceConnection aConnection) { if (Utils.IsFilePathValid(arPath)) return GetServerFileState(FileSpec.LocalSpec(Utils.AssetPathToLocalPath(arPath)), aConnection); else return FileState.None; } /// /// Returns the state of the passed in file on the server (if checked out by someone else for instance) /// public static FileState GetServerFileState(FileSpec arFile, PerforceConnection aConnection) { FileState retState = FileState.None; IList dataList = GetFileMetaData(aConnection, null, arFile); if (dataList != null) { foreach (var data in dataList) { if (data.OtherActions != null) { foreach (var action in data.OtherActions) { FileState otherState = ParseFileAction(action); if (otherState != FileState.InDepot) { retState = otherState; } } } } } return retState; } /// /// Returns the state of the passed in file on the server (if checked out by someone else for instance) /// public static RevisionState GetRevisionState(string arPath, PerforceConnection aConnection) { if (Utils.IsFilePathValid(arPath)) return GetRevisionState(FileSpec.LocalSpec(Utils.AssetPathToLocalPath(arPath)), aConnection); else return RevisionState.None; } /// /// Returns the state of the passed in file on the server (if checked out by someone else for instance) /// public static RevisionState GetRevisionState(FileSpec aFile, PerforceConnection aConnection) { RevisionState retState = RevisionState.None; IList dataList = GetFileMetaData(aConnection, null, aFile); if (dataList != null) { foreach (var data in dataList) { if (data.HaveRev == data.HeadRev) retState = RevisionState.HasLatest; else retState = RevisionState.OutOfDate; } } return retState; } /// /// Performs an operation after opening a perforce connection /// public static void PerformConnectionOperation(System.Action aConnectionOperation, bool async = false) { if (Config.ValidConfiguration) { PerforceConnection connection = new PerforceConnection(); try // The try block will make sure the connection is Disposed (i.e. closed) { // Perform the operation aConnectionOperation(connection); } catch (Exception ex) { log.Error("Operation Error:", ex); if (!async) { LogP4Exception(ex); } } finally { connection.Dispose(); } } else { log.Error("Configuration Invalid"); } } /// /// Performs an operation after opening a perforce connection and getting the opened files /// //public static void PerformOpenConnectionOperation(System.Action aConnectionOperation) //{ // PerformConnectionOperation(con => aConnectionOperation(new OpenedConnection(con))); //} /// /// Submits the files to the server /// public static void SubmitFiles(PerforceConnection aConnection, string aChangeListDescription, List aFiles) { #if DEBUG log.DebugFormat("files: {0}", Logger.FileSpecListToString(aFiles)); #endif // Move all the files to a new changelist Changelist changeList = new Changelist(); changeList.Description = aChangeListDescription; changeList.ClientId = Config.Workspace; var allFilesMetaRaw = GetFileMetaData(aConnection, aFiles, null); List allFilesMeta = new List(); Utils.GetMatchingMetaData(aFiles, allFilesMetaRaw, allFilesMeta); List lockedFiles = new List(); for (int i = 0; i < aFiles.Count; i++) { var metaData = allFilesMeta[i]; if (GetLockState(metaData) == LockState.TheirLock) { lockedFiles.Add(metaData.LocalPath); } else { changeList.Files.Add(metaData); } } bool cont = lockedFiles.Count == 0; if (cont) { #if DEBUG log.Debug("Changelist files: " + Logger.FileSpecListToString(aFiles)); //log.Debug("Changelist has " + aFiles.Count + " files"); //log.Debug("ChangeSpec is: " + changeList.ToString()); #endif Changelist repList = null; try { repList = aConnection.P4Depot.CreateChangelist(changeList); } catch (System.Exception ex) { #if DEBUG log.Debug("CreateChangelist Exception", ex); #endif Debug.LogException(ex); Debug.LogWarning("P4Connect - CreateChangelist failed, open P4V and make sure your files are in the default changelist"); if (ex is Perforce.P4.P4Exception) { Debug.LogWarning("Exception caused by this cmd: " + (ex as P4Exception).CmdLine); } cont = false; } if (cont) { Options submitFlags = new Options(SubmitFilesCmdFlags.None, -1, repList, null, null); SubmitResults sr = null; try { EditorUtility.DisplayProgressBar("Hold on", "Submitting Files", 0.5f); sr = aConnection.P4Client.SubmitFiles(submitFlags, null); } catch(Exception ex) { // may fail, cannot submit from non-stream client // may fail because we need to resolve #if DEBUG log.Error("SubmitFiles Exception", ex); #endif Debug.LogWarning("P4Connect - Submit failed, You may need to use P4V to resolve conflicts.\n" + ex.ToString()); cont = false; } finally { EditorUtility.ClearProgressBar(); } if (cont) { List submittedFiles = new List(sr.Files.Select(srec => srec.File)); Utils.LogFiles(submittedFiles, "Submitting {0}"); if (sr.Files.Count != changeList.Files.Count) { Debug.LogWarning("P4Connect - Not All files were submitted"); } } else { EditorUtility.DisplayDialog("Cannot submit files...", "Submit failed3, You may need to use P4V to resolve conflicts.", "Ok"); } // Notify that things changed either way if (OnOperationPerformed != null) { List allFilesAndMetas = new List(); foreach (var file in aFiles) { allFilesAndMetas.Add(new FileAndMeta(file, null)); } OnOperationPerformed(aConnection, allFilesAndMetas); } } else { EditorUtility.DisplayDialog("Cannot submit files...", "Submit failed4, open P4V and make sure your files are in the default changelist", "Ok"); } } else { StringBuilder builder = new StringBuilder(); builder.AppendLine("The following files are locked by someone else and cannot be submitted."); foreach (var file in lockedFiles) { builder.Append("\t" + file.LocalPath.Path); } EditorUtility.DisplayDialog("Cannot submit locked files...", builder.ToString(), "Ok"); } } /// /// Attempts to perform the operation immediately /// static List PerformOperation(string[] arPaths, string[] arMoveToPaths, AssetOperation aDesiredOp) { #if DEBUG log.DebugFormat("op: {0}, paths: {1} moveto: {2}", aDesiredOp.ToString(), Logger.StringArrayToString(arPaths), Logger.StringArrayToString(arMoveToPaths)); #endif List result = EmptyFileAndMeta; if (result == null) log.Error("result is null!"); if (Config.ValidConfiguration) { try { // The using statement will make sure the connection is Disposed (i.e. closed) using (PerforceConnection connection = new PerforceConnection()) { //List filesAndOps = new List(); //AddToFilesAndOpList(connection, arPaths, arMoveToPaths, aDesiredOp, filesAndOps); List filesAndOps = AddToFilesAndOpList(connection, arPaths, arMoveToPaths, aDesiredOp); FileListOperations operations = new FileListOperations(); AddToOperations(filesAndOps, operations); List fspecs = operations.Run(connection); // var not_needed = fspecs.JustLocalSpecs(); // var needed = fspecs.NonLocalSpecs().UnversionedSpecs(); // var local_fix = needed.FixLocalPaths(connection); // var res = not_needed.Union(local_fix); var res = fspecs.JustLocalSpecs().Union(fspecs.NonLocalSpecs().UnversionedSpecs().FixLocalPaths(connection)); result = res.ToFileAndMetas().ToList(); #if DEBUG //log.Debug("not_needed: " + Logger.FileSpecListToString(not_needed.ToList())); // log.Debug("needed: " + Logger.FileSpecListToString(needed.ToList())); // log.Debug("fixed: " + Logger.FileSpecListToString(local_fix.ToList())); //log.Debug("all: " + Logger.FileSpecListToString(res.ToList())); //log.Debug("allfam: " + Logger.FileAndMetaListToString(result)); #endif // Trigger events if (OnOperationPerformed != null) { OnOperationPerformed(connection, result); } } } catch (Exception ex) { EditorUtility.ClearProgressBar(); log.Error("... Exception ", ex); LogP4Exception(ex); } } // Any result returned from an operation may have it's status changed // so we should re-queue it AssetStatusCache.MarkAsDirty(result); #if DEBUG log.DebugFormat("results: {0}", Logger.FileAndMetaListToString(result)); #endif return result; } /// /// Given a list of asset file paths and operation, fills a list of perforce files and operation for each /// /// The perforce connection to use to query current state of files on the depot /// The list of files (these can be a mix and match of .meta files and regular files) /// Where moved files are going /// The operation to perform /// INOUT: The list of perforce files to fill out static void AddToFilesAndOpList(PerforceConnection aConnection, string[] arAssetPaths, string[] arMoveToAssetPaths, AssetOperation aDesiredOperation, List aInOutFilesAndOp) { // Build the list of files and metas List fileSpecs = new List(); // there may be null values in here List metaSpecs = new List(); // there may be null values in here List moveToFileSpecs = new List(); // there may be null values in here List moveToMetaSpecs = new List(); // there may be null values in here List areFolders = new List(); #if DEBUG log.DebugFormat("op: {0} paths: {1} to: {2} inout: {3}", aDesiredOperation.ToString(), Logger.StringArrayToString(arAssetPaths), Logger.StringArrayToString(arMoveToAssetPaths), Logger.FilesAndOpListToString(aInOutFilesAndOp)); #endif log.Debug("arAssetPaths: " + Logger.StringArrayToString(arAssetPaths)); //log.Debug("arAssetPathsNull:" + Logger.StringArrayToString(arAssetPaths.NonNullElements().ToArray())); // Set up the arrays needed to generate the operations for (int i = 0; i < arAssetPaths.Length; ++i) { string FileName = ""; string MetaName = ""; // Gets local paths for Asset and Meta Utils.GetFileAndMeta(arAssetPaths[i], out FileName, out MetaName); string MoveToFileName = ""; string MoveToMetaName = ""; if (arMoveToAssetPaths != null) { // Gets local paths for both Asset and Meta Utils.GetFileAndMeta(arMoveToAssetPaths[i], out MoveToFileName, out MoveToMetaName); } // If the operation hasn't happened yet, we can be more strict with our verifications bool isFolder = Utils.IsDirectory(Utils.LocalPathToAssetPath(FileName)); areFolders.Add(isFolder); bool isMeta = Utils.IsMetaFile(arAssetPaths[i]); // Escape filenames string escapedFileName = FileName; string escapedMetaName = MetaName; string escapedMoveToFileName = MoveToFileName; string escapedMoveToMetaName = MoveToMetaName; if (isFolder) { // Add special spec for "all subdirs" fileSpecs.Add(FileSpec.LocalSpec(escapedFileName)); if (MoveToFileName != "") moveToFileSpecs.Add(FileSpec.LocalSpec(System.IO.Path.Combine(escapedMoveToFileName, "..."))); else moveToFileSpecs.Add(null); // All Folders should have metas escapedMetaName = Utils.RemoveDirectoryWildcards(escapedMetaName); // Just the folder name, thank you. metaSpecs.Add(FileSpec.LocalSpec(escapedMetaName)); if (escapedMoveToMetaName != "") moveToMetaSpecs.Add(FileSpec.LocalSpec(escapedMoveToMetaName)); else moveToMetaSpecs.Add(null); } else if (!isMeta) // Asset File { fileSpecs.Add(FileSpec.LocalSpec(escapedFileName)); if (escapedMoveToFileName != "") moveToFileSpecs.Add(FileSpec.LocalSpec(escapedMoveToFileName)); else moveToFileSpecs.Add(null); if (ShouldFileHaveMetaFile(FileName)) // this includes folders { metaSpecs.Add(FileSpec.LocalSpec(escapedMetaName)); if (escapedMoveToMetaName != "") moveToMetaSpecs.Add(FileSpec.LocalSpec(escapedMoveToMetaName)); else moveToMetaSpecs.Add(null); } else { metaSpecs.Add(null); moveToMetaSpecs.Add(null); } } else // Must be Meta file { metaSpecs.Add(FileSpec.LocalSpec(escapedMetaName)); if (escapedMoveToMetaName != "") moveToMetaSpecs.Add(FileSpec.LocalSpec(escapedMoveToMetaName)); else moveToMetaSpecs.Add(null); // Since the non-meta file was not found, add it anyway fileSpecs.Add(FileSpec.LocalSpec(escapedFileName)); if (escapedMoveToFileName != "") moveToFileSpecs.Add(FileSpec.LocalSpec(escapedMoveToFileName)); else moveToFileSpecs.Add(null); } } if (arMoveToAssetPaths != null) { log.Debug("moveto: " + Logger.StringArrayToString(arMoveToAssetPaths.NonNullElements().ToArray())); } // log.Debug("allFiles: " + Logger.StringArrayToString(allFiles.ToArray())); //Get a Hashset of all the files we need to query HashSet statusFiles = new HashSet(fileSpecs.NonNullElements().ToLocalPaths()); statusFiles.UnionWith(moveToFileSpecs.NonNullElements().ToLocalPaths()); statusFiles.UnionWith(metaSpecs.NonNullElements().ToLocalPaths()); statusFiles.UnionWith(moveToMetaSpecs.NonNullElements().ToLocalPaths()); // This routine will check for valid asset statuses (and associated FileMetaData) for each file in the parameters. // If needed, the server will be queried var stats = AssetStatusCache.GetAssetStatusesFromPaths(aConnection, statusFiles.ToList()); #if DEBUG log.Debug("assets: " + Logger.FileSpecListToString(fileSpecs)); log.Debug("metas: " + Logger.FileSpecListToString(metaSpecs)); #endif // Now create the file operations for (int i = 0; i < arAssetPaths.Length; ++i) { // Build the FileAndMeta data FilesAndOp fileAndOp = new FilesAndOp(); fileAndOp.File = fileSpecs[i]; fileAndOp.MoveToFile = moveToFileSpecs[i]; #if DEBUG //log.DebugFormat("processing files: {0} and {1}", fileSpecs[i].ToStringNullSafe(), metaSpecs[i].ToStringNullSafe()); #endif FileMetaData filemd = AssetStatusCache.GetMetaData(fileSpecs[i]); //log.Debug("filemd: " + Logger.FileMetaDataToString(filemd)); fileAndOp.FileOp = GetFileOperation(fileSpecs[i].ToAssetPath(aConnection), aDesiredOperation, filemd); FileOperation metaOp = FileOperation.None; // Some files don't have associated metas if (metaSpecs[i] != null) { FileMetaData metamd = AssetStatusCache.GetMetaData(metaSpecs[i]); //log.Debug("metamd: " + Logger.FileMetaDataToString(metamd)); metaOp = GetFileOperation(metaSpecs[i].ToAssetPath(aConnection), aDesiredOperation, metamd); } if (metaOp == FileOperation.None) // No Meta { fileAndOp.Meta = null; fileAndOp.MoveToMeta = null; } else if (metaOp == fileAndOp.FileOp && (!areFolders[i] || fileAndOp.FileOp != FileOperation.Add)) { // Lump it in with the fileOp fileAndOp.Meta = metaSpecs[i]; fileAndOp.MoveToMeta = moveToMetaSpecs[i]; } else { // Treat the meta separately FilesAndOp metaAndOp = new FilesAndOp(); metaAndOp.FileOp = metaOp; metaAndOp.Meta = metaSpecs[i]; metaAndOp.MoveToMeta = moveToMetaSpecs[i]; aInOutFilesAndOp.Add(metaAndOp); } // Add the operation if (!areFolders[i] || fileAndOp.FileOp != FileOperation.Add) { aInOutFilesAndOp.Add(fileAndOp); } } #if DEBUG int n = 1; foreach (var fao in aInOutFilesAndOp) { log.DebugFormat("fao {0}: {1}", n.ToString(), Logger.FileAndOpToString(fao)); n++; } #endif } static public List AddToFilesAndOpList(PerforceConnection aConnection, string[] arAssetPaths, string[] arMoveToAssetPaths, AssetOperation aDesiredOperation) { #if DEBUG log.DebugFormat("op: {0} paths: {1} to: {2}", aDesiredOperation.ToString(), Logger.StringArrayToString(arAssetPaths), Logger.StringArrayToString(arMoveToAssetPaths)); #endif Collector collector = new Collector(aDesiredOperation); for(int i = 0; i < arAssetPaths.Length; i++) { string path = arAssetPaths[i]; string move = arMoveToAssetPaths == null ? null : arMoveToAssetPaths[i]; collector.Add(path, move); } return collector.GetFilesAndOps(aConnection); } public class Collector { private static readonly ILog log = LogManager.GetLogger(typeof(Collector)); AssetOperation operation; public class FAOEntry { public string file; public string meta; public string mfile; public string mmeta; public bool is_folder; public FAOEntry() { file = meta = mfile = mmeta = null; is_folder = false; } /// /// Return a collection of all the non-null paths /// /// public IEnumerable paths() { if (! is_folder && this.file != null) yield return this.file; if (this.meta != null) yield return this.meta; if (! is_folder && this.mfile != null) yield return this.mfile; if (this.mmeta != null) yield return this.mmeta; yield break; } } public Dictionary entryDict; public Collector(AssetOperation op) { operation = op; entryDict = new Dictionary(); } /// /// Add an asset with it's moveto location /// /// /// public void Add(string assetPath, string moveToAssetPath) { log.Debug("add: file: " + assetPath.ToStringNullSafe() + " mfile: " + moveToAssetPath.ToStringNullSafe()); FAOEntry fao = new FAOEntry(); string FileName = ""; string MetaName = ""; Utils.GetFileAndMeta(assetPath, out FileName, out MetaName); if (Utils.IsDirectory(assetPath)) { assetPath = Utils.AddDirectoryWildcards(assetPath); fao.is_folder = true; fao.file = assetPath; fao.mfile = moveToAssetPath == null ? null : Utils.AddDirectoryWildcards(moveToAssetPath); } else if (Utils.IsMetaFile(assetPath)) { fao.meta = assetPath; fao.mmeta = moveToAssetPath; } else { fao.file = assetPath; fao.mfile = moveToAssetPath; } // Look it up FAOEntry entry; if (entryDict.TryGetValue(FileName, out entry)) { // Update the entry in the dictionary if (entry.file == null && fao.file != null) { entry.file = fao.file; } if (entry.mfile == null && fao.mfile != null) { entry.mfile = fao.mfile; } if (entry.meta == null && fao.meta != null) { entry.meta = fao.meta; } if (entry.mmeta == null && fao.mmeta != null) { entry.mmeta = fao.mmeta; } } else // Create a new entry in the dictionary { entryDict[FileName] = fao; } } /// /// Return the list of FilesAndOp to be executed by the engine /// /// public List GetFilesAndOps(PerforceConnection aConnection) { List results = new List(); HashSet assets = new HashSet(); // Check to see if any metas are missing foreach(FAOEntry entry in entryDict.Values) { // add a meta file if needed if (entry.file != null && entry.meta == null && (entry.is_folder || ShouldFileHaveMetaFile(entry.file))) { entry.meta = Utils.GetMetaFile(Utils.RemoveDirectoryWildcards(entry.file)); } // add a move to meta file if needed if (entry.mfile != null && entry.mmeta == null && (entry.is_folder || ShouldFileHaveMetaFile(entry.mfile))) { entry.mmeta = Utils.GetMetaFile(Utils.RemoveDirectoryWildcards(entry.mfile)); } // Collect all paths into the "assets" collection assets.UnionWith(entry.paths()); } List tostat = assets.StripDirectories().ToList(); // This routine will check for valid asset statuses (and associated FileMetaData) for each file in the parameters. // If needed, the server will be queried var stats = AssetStatusCache.GetAssetStatusesFromPaths(aConnection, tostat); #if DEBUG //log.Debug("stats: " + Logger.AssetStatusListToString(stats)); #endif // Compute the operation for each entry foreach (FAOEntry entry in entryDict.Values) { FileOperation op = FileOperation.None; FileOperation metaop = FileOperation.None; if (entry.file != null) { FileMetaData filemd = AssetStatusCache.GetMetaData(entry.file); //log.Debug("filemd: " + Logger.FileMetaDataToString(filemd)); op = GetFileOperation(entry.file, operation, filemd); } if (entry.meta != null) { FileMetaData metamd = AssetStatusCache.GetMetaData(entry.meta); //log.Debug("metamd: " + Logger.FileMetaDataToString(metamd)); metaop = GetFileOperation(entry.meta, operation, metamd); } if (op == metaop) { // One record sharing an op results.Add(new FilesAndOp(entry.file, entry.meta, entry.mfile, entry.mmeta, op)); } else { // file and meta file have different ops, so they need different entries if (entry.file != null) { results.Add(new FilesAndOp(entry.file, null, entry.mfile, null, op)); } if (entry.meta != null) { results.Add(new FilesAndOp(null, entry.meta, null , entry.mmeta, metaop)); } } } results = results.NoNoOps().ToList(); #if DEBUG int n = 1; foreach (var fao in results) { log.DebugFormat("fao {0}: {1}", n.ToString(), Logger.FileAndOpToString(fao)); n++; } #endif return results; } } // Collector Class /// /// Adds the list of perforce files (and associated .meta if applicable) to the list of files/operations /// static void AddToOperations(List aFileAndMetas, FileListOperations aInOutFileOperations) { foreach (FilesAndOp fileAndMeta in aFileAndMetas) { aInOutFileOperations.Add(fileAndMeta); } } /// /// Parses a metadata file action and translates it into a state for P4connect /// public static FileState ParseFileAction(FileAction aAction) { FileState outOp = FileState.None; switch (aAction) { case FileAction.None: outOp = FileState.None; break; case FileAction.Add: outOp = FileState.MarkedForAdd; break; case FileAction.Delete: outOp = FileState.MarkedForDelete; break; case FileAction.Edit: outOp = FileState.MarkedForEdit; break; case FileAction.MoveAdd: outOp = FileState.MarkedForAddMove; break; case FileAction.MoveDelete: outOp = FileState.MarkedForDeleteMove; break; default: //We don't really handle anything else outOp = FileState.InDepot; break; } return outOp; } /// /// Gets the default file operation, based on the asset operation /// static FileOperation GetDefaultFileOperation(AssetOperation aDesiredOperation) { FileOperation outOp = FileOperation.None; switch (aDesiredOperation) { case AssetOperation.None: outOp = FileOperation.None; break; case AssetOperation.Add: outOp = FileOperation.Add; break; case AssetOperation.Remove: outOp = FileOperation.Delete; break; case AssetOperation.Checkout: outOp = FileOperation.Checkout; break; case AssetOperation.Move: outOp = FileOperation.Move; break; case AssetOperation.Revert: outOp = FileOperation.Revert; break; case AssetOperation.RevertIfUnchanged: outOp = FileOperation.RevertIfUnchanged; break; case AssetOperation.GetLatest: outOp = FileOperation.GetLatest; break; case AssetOperation.ForceGetLatest: outOp = FileOperation.ForceGetLatest; break; case AssetOperation.Lock: outOp = FileOperation.Lock; break; case AssetOperation.Unlock: outOp = FileOperation.Unlock; break; } return outOp; } /// /// Determines what Perforce operation to perform on the passed in file, given what the user wants to do and what the current state of the file /// /// FileSpec for file /// Desired Perforce operation /// File MetaData from fstat command /// File Operation to use static FileOperation GetFileOperation(string aFile, AssetOperation aDesiredOperation, FileMetaData fmd) { FileState aOpenAction = FileState.IsDirectory; FileState aHeadAction = FileState.None; LockState aCurrentLockState = LockState.None; if (! Utils.IsDirectory(aFile)) { aOpenAction = GetOpenAction(fmd); aHeadAction = GetHeadAction(fmd); aCurrentLockState = GetLockState(fmd); } FileOperation outOp = FileOperation.None; #if DEBUG // log.DebugFormat("desired: {0} file: {1} state: {2} lock: {3} ", // aDesiredOperation.ToString(), // aFile == null ? "null" : aFile.ToString(), // aCurrentAction.ToString(), aCurrentLockState); #endif // Mark Files that are in the depot if (aOpenAction == FileState.None && (fmd.IsMapped || fmd.HeadAction != FileAction.None)) aOpenAction = FileState.InDepot; // Mark Files that are deleted in their latest revision if (aOpenAction == FileState.InDepot && (fmd.HeadAction == FileAction.Delete || fmd.HeadAction == FileAction.MoveDelete)) aOpenAction = FileState.InDepotDeleted; switch (aOpenAction) { case FileState.None: switch (aDesiredOperation) { case AssetOperation.None: outOp = FileOperation.None; break; case AssetOperation.Add: outOp = FileOperation.Add; break; case AssetOperation.Remove: outOp = FileOperation.None; break; case AssetOperation.Checkout: // trying to checkout something not in the depot, Utils.LogFileWarning(aFile, " isn't in the depot and cannot be checked out, the file will be marked for add instead"); outOp = FileOperation.Add; break; case AssetOperation.Move: // trying to move something not in the depot, add it instead Utils.LogFileWarning(aFile, " isn't in the depot and cannot be moved, the file will be marked for add instead"); outOp = FileOperation.Add; break; case AssetOperation.Revert: // trying to revert something not in the depot Utils.LogFileWarning(aFile, " isn't in the depot and cannot be reverted"); outOp = FileOperation.None; break; case AssetOperation.RevertIfUnchanged: // trying to revert something not in the depot Utils.LogFileWarning(aFile, " isn't in the depot and cannot be reverted"); outOp = FileOperation.None; break; case AssetOperation.GetLatest: // trying to sync something not in the depot // Utils.LogFileWarning(aFile, " isn't in the depot and cannot be synced"); outOp = FileOperation.None; break; case AssetOperation.ForceGetLatest: // trying to revert something not in the depot // Utils.LogFileWarning(aFile, " isn't in the depot and cannot be synced"); outOp = FileOperation.None; break; case AssetOperation.Lock: // trying to lock something not in the depot Utils.LogFileWarning(aFile, " isn't in the depot and cannot be locked"); outOp = FileOperation.None; break; case AssetOperation.Unlock: // trying to unlock something not in the depot Utils.LogFileWarning(aFile, " isn't in the depot and cannot be unlocked"); outOp = FileOperation.None; break; } break; case FileState.InDepot: switch (aDesiredOperation) { case AssetOperation.None: outOp = FileOperation.None; break; case AssetOperation.Add: // The file is already in the depot, Just Check it out outOp = FileOperation.Checkout; break; case AssetOperation.Remove: outOp = FileOperation.Delete; break; case AssetOperation.Checkout: outOp = FileOperation.Checkout; break; case AssetOperation.Move: outOp = FileOperation.Move; break; case AssetOperation.Revert: outOp = FileOperation.None; break; case AssetOperation.RevertIfUnchanged: outOp = FileOperation.None; break; case AssetOperation.GetLatest: outOp = FileOperation.GetLatest; break; case AssetOperation.ForceGetLatest: outOp = FileOperation.ForceGetLatest; break; case AssetOperation.Lock: outOp = GetLockOperation(aFile, aCurrentLockState); break; case AssetOperation.Unlock: outOp = GetUnlockOperation(aFile, aCurrentLockState); break; } break; case FileState.InDepotDeleted: switch (aDesiredOperation) { case AssetOperation.None: outOp = FileOperation.None; break; case AssetOperation.Add: outOp = FileOperation.Add; break; case AssetOperation.Remove: outOp = FileOperation.None; break; case AssetOperation.Checkout: outOp = FileOperation.Checkout; break; case AssetOperation.Move: outOp = FileOperation.Move; break; case AssetOperation.Revert: outOp = FileOperation.None; break; case AssetOperation.RevertIfUnchanged: outOp = FileOperation.None; break; case AssetOperation.GetLatest: outOp = FileOperation.GetLatest; break; case AssetOperation.ForceGetLatest: outOp = FileOperation.ForceGetLatest; break; case AssetOperation.Lock: outOp = GetLockOperation(aFile, aCurrentLockState); break; case AssetOperation.Unlock: outOp = GetUnlockOperation(aFile, aCurrentLockState); break; } break; case FileState.MarkedForEdit: switch (aDesiredOperation) { case AssetOperation.None: outOp = FileOperation.None; break; case AssetOperation.Add: // Already in the depot and checked out, do nothing outOp = FileOperation.None; break; case AssetOperation.Remove: outOp = FileOperation.RevertAndDelete; break; case AssetOperation.Checkout: outOp = FileOperation.None; break; case AssetOperation.Move: // Already in the depot and checked out, just flag it for a move outOp = FileOperation.MoveToNewLocation; break; case AssetOperation.Revert: outOp = FileOperation.Revert; break; case AssetOperation.RevertIfUnchanged: outOp = FileOperation.RevertIfUnchanged; break; case AssetOperation.GetLatest: outOp = FileOperation.GetLatest; break; case AssetOperation.ForceGetLatest: outOp = FileOperation.ForceGetLatest; break; case AssetOperation.Lock: outOp = GetLockOperation(aFile, aCurrentLockState); break; case AssetOperation.Unlock: outOp = GetUnlockOperation(aFile, aCurrentLockState); break; } break; case FileState.MarkedForAdd: switch (aDesiredOperation) { case AssetOperation.None: outOp = FileOperation.None; break; case AssetOperation.Add: outOp = FileOperation.None; break; case AssetOperation.Remove: outOp = FileOperation.Revert; break; case AssetOperation.Checkout: // Already marked for add outOp = FileOperation.None; break; case AssetOperation.Move: outOp = FileOperation.RevertAndAddNoOverwrite; break; case AssetOperation.Revert: outOp = FileOperation.Revert; break; case AssetOperation.RevertIfUnchanged: outOp = FileOperation.RevertIfUnchanged; break; case AssetOperation.GetLatest: Utils.LogFileWarning(aFile, " is marked as add and cannot be synced, the file will be skipped"); outOp = FileOperation.None; break; case AssetOperation.ForceGetLatest: Utils.LogFileWarning(aFile, " is marked as add and cannot be synced, the file will be skipped"); outOp = FileOperation.None; break; case AssetOperation.Lock: outOp = GetLockOperation(aFile, aCurrentLockState); break; case AssetOperation.Unlock: outOp = GetUnlockOperation(aFile, aCurrentLockState); break; } break; case FileState.MarkedForDelete: switch (aDesiredOperation) { case AssetOperation.None: outOp = FileOperation.None; break; case AssetOperation.Add: outOp = FileOperation.RevertAndCheckoutNoOverwrite; break; case AssetOperation.Remove: outOp = FileOperation.None; break; case AssetOperation.Checkout: outOp = FileOperation.None; break; case AssetOperation.Move: outOp = FileOperation.RevertAndMove; break; case AssetOperation.Revert: outOp = FileOperation.Revert; break; case AssetOperation.RevertIfUnchanged: outOp = FileOperation.None; break; case AssetOperation.GetLatest: Utils.LogFileWarning(aFile, " is marked for delete, the file will be skipped"); outOp = FileOperation.None; break; case AssetOperation.ForceGetLatest: Utils.LogFileWarning(aFile, " is marked for delete, the file will be reverted and then synced"); outOp = FileOperation.RevertAndGetLatest; break; case AssetOperation.Lock: outOp = GetLockOperation(aFile, aCurrentLockState); break; case AssetOperation.Unlock: outOp = GetUnlockOperation(aFile, aCurrentLockState); break; } break; case FileState.MarkedForAddMove: switch (aDesiredOperation) { case AssetOperation.None: outOp = FileOperation.None; break; case AssetOperation.Add: outOp = FileOperation.None; break; case AssetOperation.Remove: outOp = FileOperation.RevertAndDelete; break; case AssetOperation.Checkout: outOp = FileOperation.None; break; case AssetOperation.Move: outOp = FileOperation.MoveToNewLocation; break; case AssetOperation.Revert: outOp = FileOperation.Revert; break; case AssetOperation.RevertIfUnchanged: outOp = FileOperation.None; break; case AssetOperation.GetLatest: Utils.LogFileWarning(aFile, " is marked for move/add, the file will be skipped"); outOp = FileOperation.None; break; case AssetOperation.ForceGetLatest: Utils.LogFileWarning(aFile, " is marked for move/add, the file will be reverted and then synced"); outOp = FileOperation.RevertAndGetLatest; break; case AssetOperation.Lock: outOp = GetLockOperation(aFile, aCurrentLockState); break; case AssetOperation.Unlock: outOp = GetUnlockOperation(aFile, aCurrentLockState); break; } break; case FileState.MarkedForDeleteMove: switch (aDesiredOperation) { case AssetOperation.None: outOp = FileOperation.None; break; case AssetOperation.Add: outOp = FileOperation.RevertAndCheckoutNoOverwrite; break; case AssetOperation.Remove: outOp = FileOperation.None; break; case AssetOperation.Checkout: outOp = FileOperation.None; break; case AssetOperation.Move: outOp = FileOperation.RevertAndMoveNoOverwrite; break; case AssetOperation.Revert: outOp = FileOperation.Revert; break; case AssetOperation.RevertIfUnchanged: outOp = FileOperation.None; break; case AssetOperation.GetLatest: Utils.LogFileWarning(aFile, " is marked for move/delete, the file will be skipped"); outOp = FileOperation.None; break; case AssetOperation.ForceGetLatest: Utils.LogFileWarning(aFile, " is marked for move/delete, the file will be reverted and then synced"); outOp = FileOperation.RevertAndGetLatest; break; case AssetOperation.Lock: outOp = GetLockOperation(aFile, aCurrentLockState); break; case AssetOperation.Unlock: outOp = GetUnlockOperation(aFile, aCurrentLockState); break; } break; case FileState.IsDirectory: switch (aDesiredOperation) { case AssetOperation.None: outOp = FileOperation.None; break; case AssetOperation.Add: outOp = FileOperation.Add; break; case AssetOperation.Remove: outOp = FileOperation.Delete; break; case AssetOperation.Checkout: outOp = FileOperation.Checkout; break; case AssetOperation.Move: outOp = FileOperation.Move; break; case AssetOperation.Revert: outOp = FileOperation.Revert; break; case AssetOperation.RevertIfUnchanged: outOp = FileOperation.RevertIfUnchanged; break; case AssetOperation.GetLatest: outOp = FileOperation.GetLatest; break; case AssetOperation.ForceGetLatest: outOp = FileOperation.ForceGetLatest; break; case AssetOperation.Lock: outOp = GetLockOperation(aFile, aCurrentLockState); break; case AssetOperation.Unlock: outOp = GetUnlockOperation(aFile, aCurrentLockState); break; } break; } #if DEBUG log.DebugFormat("OP: {0} <= desired: {1} file: {2} state: {3} lock: {4} ", outOp.ToString(), aDesiredOperation.ToString(), aFile.ToString(), aOpenAction.ToString(), aCurrentLockState); // log.DebugFormat("returns: {0}", outOp.ToString()); #endif return outOp; } static FileOperation GetLockOperation(string aFile, LockState aCurrentLockState) { if (aCurrentLockState == LockState.None) return FileOperation.Lock; else { if (aCurrentLockState == LockState.OurLock) { Utils.LogFileWarning(aFile, " is already locked, the file will be skipped"); } else { Utils.LogFileWarning(aFile, " is locked by someone else, the file will be skipped"); } return FileOperation.None; } } static FileOperation GetUnlockOperation(string aFile, LockState aCurrentLockState) { if (aCurrentLockState == LockState.OurLock) return FileOperation.Unlock; else { if (aCurrentLockState == LockState.TheirLock) { Utils.LogFileWarning(aFile, " is not locked by you, the file will be skipped"); } else { Utils.LogFileWarning(aFile, " is not locked, the file will be skipped"); } return FileOperation.None; } } /// /// Logs a P4 exception and display an accompanying message /// static void LogP4Exception(Exception ex) { Debug.LogException(ex); Debug.LogWarning("P4Connect - P4Connect encountered the following internal exception, your file changes may not be properly checked out/added/deleted."); // Parse exception text for dialog box EditorUtility.DisplayDialog("Perforce Exception", "P4Connect encountered the following exception:\n\n" + ex.Message, "Ok"); //+ "\nP4Connect will disable itself to prevent further problems. Please go to Edit->Perforce Settings to correct the issue.", "Ok"); // Config.NeedToCheckSettings(); } /// /// Helper method that filters files that shouldn't have a .meta file associated with them /// /// /// static bool ShouldFileHaveMetaFile(string arPath) { bool bret = true; // Maybe this is a project settings file, those don't seem to have .meta files with them if (arPath.Contains("ProjectSettings")) bret = false; if (arPath.Contains("DotSettings")) bret = false; if (arPath.Contains(".unityproj")) bret = false; if (arPath.Contains(".csproj")) bret = false; if (arPath.Contains(".sln")) bret = false; if (arPath.EndsWith(".meta")) bret = false; if (arPath == "Assets") bret = false; if (arPath == "") bret = false; return bret; } /// /// Utility method that adds all non null files and meta files from a list of pairs /// static List NonNullFilesAndMeta(IEnumerable aFilesAndMeta) { List retList = new List(); foreach (var fileAndMeta in aFilesAndMeta) { if (fileAndMeta.File != null) retList.Add(fileAndMeta.File); if (fileAndMeta.Meta != null) retList.Add(fileAndMeta.Meta); } return retList; } public enum WildcardCheck { None, // No wildcards detected Force, // Wildcards detected but user agreed to force the operation Cancel, // Wildcards detected and user canceled } /// /// Checks for wildcards and asks the user whether they want to force the operation /// public static WildcardCheck VerifyWildcards(IEnumerable aFiles) { var allFiles = NonNullFilesAndMeta(aFiles); List filesWithWildCards = new List(); foreach (var file in allFiles) { string filename = Utils.GetFilename(file, false); if (filename.IndexOfAny(new char[] { '*', '%', '#', '@' }) != -1) { filesWithWildCards.Add(filename); } } bool hasWildcards = filesWithWildCards.Count > 0; if (hasWildcards) { if (Config.WarnOnSpecialCharacters) { StringBuilder builder = new StringBuilder(); builder.AppendLine("The following files have special characters in them, are you sure you want to add them to the depot?"); foreach (string file in filesWithWildCards) { builder.AppendLine("\t" + file); } builder.AppendLine("Note: You can disable this warning in the Perforce Settings"); if (EditorUtility.DisplayDialog("Special Characters", builder.ToString(), "Ok", "Cancel")) { return WildcardCheck.Force; } else { return WildcardCheck.Cancel; } } else { return WildcardCheck.Force; } } else { return WildcardCheck.None; } } /// /// Performs a Get-Latest operation, and then checks to see if there are folders that need to be deleted or conflicts /// static IList PerformGetLastestAndPostChecks(PerforceConnection aConnection, List aOriginals, Options aOptions) { var result = aConnection.P4Client.SyncFiles(aOriginals, aOptions); if (result != null) { Utils.LogFiles(result, "Syncing {0}"); List allFolderDepotMetaFiles = new List(); foreach (FileSpec spec in result) { string depotPath = spec.DepotPath.Path; if (depotPath.EndsWith(".meta")) { string assetPath = Utils.AssetFromMeta(depotPath); if (Utils.IsDirectory(Utils.LocalPathToAssetPath(Utils.AssetFromMeta(spec.LocalPath.Path)))) { // We found the meta of a folder, add it allFolderDepotMetaFiles.Add(FileSpec.DepotSpec(depotPath)); } } } // Get the folder meta data if (allFolderDepotMetaFiles.Count > 0) { IList allFolderMetaMeta = GetFileMetaData(aConnection, allFolderDepotMetaFiles, null); if (allFolderMetaMeta != null) { for (int i = 0; i < allFolderMetaMeta.Count; ++i) { // Check to see if the file has been deleted if ((allFolderMetaMeta[i].HeadAction == FileAction.Delete || allFolderMetaMeta[i].HeadAction == FileAction.MoveDelete)) { // Delete the local folder string fullpath = Utils.DepotPathToFullPath(aConnection, new FileSpec(allFolderMetaMeta[i])); string folderPath = Utils.AssetFromMeta(fullpath); if (System.IO.Directory.Exists(folderPath)) { // Check that the folder is empty of files var files = System.IO.Directory.GetFiles(folderPath, "*.*", System.IO.SearchOption.AllDirectories); if (files == null || files.Length == 0) { try { System.IO.Directory.Delete(folderPath, true); } catch (System.IO.DirectoryNotFoundException) { // Ignore missing folders, they may have already been deleted } } else { Debug.LogWarning("P4Connect - " + Utils.FullPathToAssetPath(fullpath) + " was deleted, but the matching directory still contains undeleted files and so was kept"); } } } } } } // Check for conflicts by getting the state of original files List originalsAgain = new List(); foreach (FileSpec spec in aOriginals) { originalsAgain.Add(FileSpec.LocalSpec(spec.LocalPath.Path)); } // Get the local meta data IList allLocalMeta = GetFileMetaData(aConnection, originalsAgain, null); if (allLocalMeta != null) { List unresolvedFiles = new List(); foreach (FileMetaData meta in allLocalMeta) { if (meta.Unresolved) { unresolvedFiles.Add(meta); } } if (unresolvedFiles.Count > 0) { System.Text.StringBuilder builder = new StringBuilder(); builder.AppendLine("The following files have unresolved conflicts as a result of the Get Latest Operation"); foreach (var meta in unresolvedFiles) { builder.AppendLine(meta.LocalPath.Path); } builder.AppendLine("You should launch P4V and resolve the conflict before continuing your work"); EditorUtility.DisplayDialog("Unresolved Files Detected", builder.ToString(), "Ok"); } } } return result; } public static IList GetFileMetaData(PerforceConnection aConnection, Options aOptions, params FileSpec[] aSpecs) { if (Config.DisplayP4Timings) { DateTime startTimestamp = DateTime.Now; var ret = aConnection.P4Depot.GetFileMetaData(aOptions, aSpecs); double deltaInnerTime = (DateTime.Now - startTimestamp).TotalMilliseconds; aConnection.AppendTimingInfo("GetFileMetaDataTime " + deltaInnerTime.ToString() + " ms for " + aSpecs.Length + " files (" + (ret != null ? ret.Count : 0).ToString() + " retrieved)"); return ret; } else { return aConnection.P4Depot.GetFileMetaData(aOptions, aSpecs); } } /// /// Query server to get FileMetaData for each element of the FileSpec list /// /// Connection to Perforce server /// IList of FileSpec to match /// p4 command options /// null or IList of FileMetaData matching the FileSpecs public static IList GetFileMetaData(PerforceConnection aConnection, IList aSpecs, Options aOptions) { IList ret = new List(); if (aSpecs != null && aSpecs.Count > 0) { // Debug.Log("GetFileMetaData: " + Logger.FileSpecListToString(aSpecs)); if (Config.DisplayP4Timings) { DateTime startTimestamp = DateTime.Now; ret = aConnection.P4Depot.GetFileMetaData(aSpecs, aOptions); double deltaInnerTime = (DateTime.Now - startTimestamp).TotalMilliseconds; aConnection.AppendTimingInfo("GetFileMetaDataTime " + deltaInnerTime.ToString() + " ms for " + aSpecs.Count + " files (" + (ret != null ? ret.Count : 0).ToString() + " retrieved)"); } else { ret = aConnection.P4Depot.GetFileMetaData(aSpecs, aOptions); //log.Debug("metadata: " + Logger.FileMetaDataListToString(ret)); } } //Debug.Log("GetFileMetaData Returns: " + Logger.FileMetaDataListToString(ret)); // since we have fresh metadata from the server, we should also update the status cache if (ret != null) AssetStatusCache.StorePerforceMetaData(ret); return ret; } } }