using UnityEditor; using UnityEngine; using System; using System.Threading; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using Perforce.P4; using log4net; namespace P4Connect { /// <summary> /// Stores and periodically updates the Perforce status of all the assets /// </summary> public class AssetStatusCache { private static readonly ILog log = LogManager.GetLogger(typeof(AssetStatusCache)); /// <summary> /// Simple asset status struct /// Stores the file state (checked out, modified, etc...) and revision state /// </summary> public struct AssetStatus { public DateTime TimeStamp; public FileState LocalState; public FileState OtherState; public DepotState DepotState; public RevisionState RevisionState; public ResolvedState ResolvedState; public StorageType StorageType; public LockState LockState; // Use this to create an AssetStatus for a local file (perforce would return null from the fstat call) public AssetStatus(string assetPath) { TimeStamp = DateTime.UtcNow; LocalState = FileState.None; OtherState = FileState.None; DepotState = DepotState.None; RevisionState = P4Connect.RevisionState.None; ResolvedState = P4Connect.ResolvedState.None; StorageType = StorageType.Text; LockState = LockState.None; } public override string ToString() { StringBuilder sb = new StringBuilder(); sb.AppendFormat("TimeStamp: {0} ", TimeStamp); sb.AppendFormat("LocalState: {0} ", LocalState); sb.AppendFormat("OtherState: {0} ", OtherState); sb.AppendFormat("DepotState: {0} ", DepotState); sb.AppendFormat("Revision: {0} ", RevisionState); sb.AppendFormat("Resolved: {0} ", ResolvedState); sb.AppendFormat("Storage: {0} ", StorageType); sb.AppendFormat("Lockstate: {0} ", LockState); return (sb.ToString()); } public bool IsOnServer() { return (LocalState != FileState.None && LocalState != FileState.MarkedForAdd); } public bool IsLocked() { return (LockState == LockState.OurLock || LockState == LockState.TheirLock); } public bool IsOpened() { return (! (LocalState == FileState.None || LocalState == FileState.InDepot)); } } /// <summary> /// This delegate is triggered when the status of an asset changes /// </summary> public delegate void OnAssetStatusChangedDelegate(PerforceConnection aConnection); public static event OnAssetStatusChangedDelegate OnAssetStatusChanged { add { if (_OnAssetStatusChanged == null) { // We had no reason to get updated before, so do it now Engine.OnOperationPerformed += OnEngineOperationPerformed; } _OnAssetStatusChanged += value; } remove { _OnAssetStatusChanged -= value; if (_OnAssetStatusChanged == null) { // We no longer have any reason to be updated Engine.OnOperationPerformed -= OnEngineOperationPerformed; } } } // The internal event that is actually registered with static event OnAssetStatusChangedDelegate _OnAssetStatusChanged; // The cache static Dictionary<string, AssetStatus> _Statuses; public static void Initialize() { _Statuses = new Dictionary<string, AssetStatusCache.AssetStatus>(); } /// <summary> /// Generic method that fetches the cached asset status (checked out, out of date, etc...) /// </summary> public static AssetStatus GetAssetStatus(string aAssetPath) { AssetStatus ret = new AssetStatus(); if (_Statuses != null) { _Statuses.TryGetValue(GetEffectivePath(aAssetPath), out ret); } return ret; } /// <summary> /// Create an asset status (checked out, out of date, etc...) /// Faster on multiple files because it batches the P4 requests /// Guaranteed to have one AssetStatus per AssetPath /// </summary> public static void GetAssetData(PerforceConnection aConnection, List<string> aAssetPaths, List<AssetStatus> aOutStatuses) { if (! Config.ValidConfiguration) return; DateTime startTimestamp = DateTime.Now; List<FileSpec> localFiles = GetFileSpecs(aAssetPaths); GetAssetData(aConnection, localFiles, aOutStatuses); // Cache the AssetStatuses in the _Statuses dictionary for (int i = 0; i < aAssetPaths.Count; ++i) { _Statuses[GetEffectivePath(aAssetPaths[i])] = aOutStatuses[i]; } if (Config.DisplayP4Timings) { double deltaInnerTime = (DateTime.Now - startTimestamp).TotalMilliseconds; aConnection.AppendTimingInfo("GetAssetData " + deltaInnerTime.ToString() + " ms for " + aAssetPaths.Count + " files (" + aOutStatuses.Count + " retrieved)"); } } /// <summary> /// Force P4Connect to update the status of a file in its status cache /// </summary> /// <param name="aAssetPaths">List of Linux style Project Relative Paths</param> public static void UpdateAssetData(List<string> aAssetPaths) { if (! Config.ValidConfiguration) return; //Debug.Log("paths: " + Logger.StringArrayToString(aAssetPaths.ToArray())); List<FileSpec> localFiles = GetFileSpecs(aAssetPaths); List<AssetStatus> statuses = new List<AssetStatus>(); //Debug.Log("Filespecs: " + Logger.FileSpecListToString(localFiles)); Engine.PerformConnectionOperation(con => { GetAssetData(con, localFiles, statuses); }); if (statuses.Count == aAssetPaths.Count) { for (int i = 0; i < aAssetPaths.Count; ++i) { _Statuses[GetEffectivePath(aAssetPaths[i])] = statuses[i]; } } } /// <summary> /// Given the meta data, figures out the P4Connect icon representation for the file /// There is only a subset of all the metadata that P4Connect actually displays /// </summary> static AssetStatus CreateAssetStatus(FileMetaData aMeta) { //Debug.Log("CreateAssetStatus: " + Logger.FileMetaDataToString(aMeta)); AssetStatus retData = new AssetStatus(); if (aMeta == null) return retData; // Fill out the struct switch (aMeta.Type.BaseType) { case BaseFileType.Text: retData.StorageType = StorageType.Text; break; case BaseFileType.Binary: retData.StorageType = StorageType.Binary; break; default: retData.StorageType = StorageType.Other; break; } retData.LocalState = Engine.ParseFileAction(aMeta.Action); if (retData.LocalState == FileState.None) { // Check to see if Perforce knows this file so we can override the localState to InDepot. if (aMeta.IsMapped || aMeta.HeadAction != FileAction.None) retData.LocalState = FileState.InDepot; } if (aMeta.OtherActions != null) { retData.OtherState = Engine.ParseFileAction(aMeta.OtherActions[0]); } if (aMeta.HaveRev >= aMeta.HeadRev) retData.RevisionState = RevisionState.HasLatest; else retData.RevisionState = RevisionState.OutOfDate; if (aMeta.Unresolved) retData.ResolvedState = ResolvedState.NeedsResolve; else retData.ResolvedState = ResolvedState.None; if (aMeta.HeadAction == FileAction.MoveDelete || aMeta.HeadAction == FileAction.Delete) retData.DepotState = DepotState.Deleted; else retData.DepotState = DepotState.None; retData.TimeStamp = DateTime.Now; retData.LockState = Engine.GetLockState(aMeta); // Debug.Log("AssetStatus: " + retData.ToString()); return retData; } /// <summary> /// This functions gets called when P4Connect does *something* to files /// In this case we update the files status /// </summary> static void OnEngineOperationPerformed(PerforceConnection aConnection, List<FileAndMeta> aFilesAndMetas) { if (aFilesAndMetas.Count > 0) { List<FileSpec> allSpecs = new List<FileSpec>(); foreach (var fileAndMeta in aFilesAndMetas) { if (fileAndMeta.File != null) { allSpecs.Add(fileAndMeta.File); } if (fileAndMeta.Meta != null) { allSpecs.Add(fileAndMeta.Meta); } } allSpecs = allSpecs.ValidSpecs().ToList(); List<AssetStatus> allStatuses = new List<AssetStatus>(); GetAssetData(aConnection, allSpecs, allStatuses); // Cache the returned values for (int i = 0; i < allSpecs.Count; ++i) { string path = allSpecs[i].ToAssetPath(aConnection); if (! string.IsNullOrEmpty(path)) { _Statuses[GetEffectivePath(path)] = allStatuses[i]; #if DEBUG log.Debug("_Statuses[" + GetEffectivePath(path) + "] = " + allStatuses[i].ToString()); #endif } } if (_OnAssetStatusChanged != null) { _OnAssetStatusChanged(aConnection); } } } /// <summary> /// Using the Perforce connection, get a matching AssetStatus for each FileSpec /// If the metadata for a file is not in Perforce, we provide a "local" AssetStatus instead. /// </summary> /// <param name="aConnection">Perforce Connection</param> /// <param name="aFiles"> Files to look up </param> /// <param name="aOutStatuses">AssetStatus List returned, one for each File</param> static void GetAssetData(PerforceConnection aConnection, List<FileSpec> aFiles, List<AssetStatus> aOutStatuses) { //DateTime startTimestamp = DateTime.Now; if (aFiles.Count > 0) { Options fsopts = new Options(); fsopts["-Op"] = null; // Return "real" clientpaths //Debug.Log("GetAssetData: " + Logger.FileSpecListToString(aFiles)); Dictionary<string, FileMetaData> dict = new Dictionary<string, FileMetaData>(); // Create a Dictionary from any non-null response. var mdlist = Engine.GetFileMetaData(aConnection, aFiles, fsopts); if (mdlist != null) { //Debug.Log("AssetData: " + Logger.FileMetaDataListToString(mdlist)); foreach(var md in mdlist) { dict.Add(md.LocalPath.Path, md); } } //if (Config.DisplayP4Timings) //{ // double deltaInnerTime = (DateTime.Now - matchingMetaStartTime).TotalMilliseconds; // aConnection.AppendTimingInfo("GetMatchingMetaData " + deltaInnerTime.ToString() + " ms"); //} foreach( FileSpec f in aFiles) { AssetStatus status; FileMetaData md = new FileMetaData(); if (dict.TryGetValue(f.LocalPath.Path, out md)) { status = CreateAssetStatus(md); } else { status = new AssetStatus(); } aOutStatuses.Add(status); } } //Debug.Log("GetAssetData: filecount " + aFiles.Count.ToString() + " statuscount: " + aOutStatuses.Count.ToString()); //Debug.Log("GetAssetData: content " + Logger.AssetStatusListToString(aOutStatuses)); //if (Config.DisplayP4Timings) //{ // double deltaInnerTime = (DateTime.Now - startTimestamp).TotalMilliseconds; // aConnection.AppendTimingInfo("GetAssetData " + deltaInnerTime.ToString() + " ms"); //} } /// <summary> /// Returns a proper list of file spec given a list of paths, accounting for directories /// </summary> static List<FileSpec> GetFileSpecs(List<string> aAssetPaths) { List<FileSpec> localFiles = new List<FileSpec>(); foreach (var path in aAssetPaths) { localFiles.Add(FileSpec.LocalSpec(Utils.AssetPathToLocalPath(GetEffectivePath(path)))); } return localFiles; } /// <summary> /// Returns the path to use when printing the name, or looking up status info /// </summary> static string GetEffectivePath(string aAssetPath) { if (Utils.IsDirectory(aAssetPath)) { return Utils.MetaFromAsset(aAssetPath); } else { return aAssetPath; } } } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#11 | 16210 | Norman Morse | Remove files from old locations | ||
#10 | 15383 | Norman Morse |
Improved Diagnostics, cleaned up unnecessary log output Moved some Dialog Initialization to OnEnable() Fixed Unity 5.1.1 incompatibilities Added Operation support for In Depot Deleted files |
||
#9 | 15244 | Norman Morse |
Better Directory support in "add" "get latest" "refresh" and other commands. Improved Project Root detection Various Bug Fixes and Clean up |
||
#8 | 15079 | Norman Morse |
Rewrote AssetStatusCache to Cache AssetStatuses and FileMetaData Fixed Edge conditions on Engine Operations Change Debug output defaults. Will now Checkout files which request to be "added" but which already exist in perforce. Output P4Connect version to log on initialization. |
||
#7 | 14232 | Norman Morse | GA.8 release | ||
#6 | 14193 | Norman Morse |
GA.7 release Refactor Pending Changes Resolve Submit issues. Fixed Menu entries. Handle mismatched file and meta states. |
||
#5 | 13824 | Norman Morse |
Changes to have fstat return true client paths. Remove versions, fixes problems with "local" paths sneaking into results. |
||
#4 | 12945 | Norman Morse | Changes from internal main GA.1 | ||
#3 | 12565 | Norman Morse |
Integrated from Dev Branch Made ChangeManager into Static Class. Improved close window behavior for when connection is invalid Fixed localOpenFiles not updating on submit |
||
#2 | 12553 | Norman Morse |
integrate from internal main Build fixes for EC. Major changes to Configuration and re-initialization code. Bug fixes |
||
#1 | 10940 | Norman Morse |
Inital Workshop release of P4Connect. Released under BSD-2 license |