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 { /// /// Stores and periodically updates the Perforce status of all the assets /// public class AssetStatusCache { private static readonly ILog log = LogManager.GetLogger(typeof(AssetStatusCache)); /// /// Simple asset status struct /// Stores the file state (checked out, modified, etc...) and revision state /// 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; } /// /// This delegate is triggered when the status of an asset changes /// 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 _Statuses; public static void Initialize() { _Statuses = new Dictionary(); } /// /// Generic method that fetches the cached asset status (checked out, out of date, etc...) /// public static AssetStatus GetAssetStatus(string aAssetPath) { AssetStatus ret = new AssetStatus(); if (_Statuses != null) { _Statuses.TryGetValue(GetEffectivePath(aAssetPath), out ret); } return ret; } /// /// Generic method that fetches the cached asset status (checked out, out of date, etc...) /// Faster on multiple files because it batches the P4 requests /// public static void GetAssetData(PerforceConnection aConnection, List aAssetPaths, List aOutStatuses) { if (! Config.ValidConfiguration) return; DateTime startTimestamp = DateTime.Now; List localFiles = GetFileSpecs(aAssetPaths); GetAssetData(aConnection, localFiles, aOutStatuses); if (aAssetPaths.Count == aOutStatuses.Count) { 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)"); } } /// /// Force P4Connect to update the status of a file in its status cache /// /// List of Linux style Project Relative Paths public static void UpdateAssetData(List aAssetPaths) { if (! Config.ValidConfiguration) return; // Debug.Log("paths: " + Logger.StringArrayToString(aAssetPaths.ToArray())); List localFiles = GetFileSpecs(aAssetPaths); List statuses = new List(); 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]; } } } /// /// 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 /// static AssetStatus CreateAssetStatus(FileMetaData aMeta) { AssetStatus retData = new AssetStatus(); // 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 (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); return retData; } /// /// This functions gets called when P4Connect does *something* to files /// In this case we update the files status /// static void OnEngineOperationPerformed(PerforceConnection aConnection, List aFilesAndMetas) { if (aFilesAndMetas.Count > 0) { List allSpecs = new List(); foreach (var fileAndMeta in aFilesAndMetas) { if (fileAndMeta.File != null) { allSpecs.Add(fileAndMeta.File); } if (fileAndMeta.Meta != null) { allSpecs.Add(fileAndMeta.Meta); } } List allStatuses = new List(); GetAssetData(aConnection, allSpecs, allStatuses); // Cache the returned values for (int i = 0; i < allSpecs.Count; ++i) { string path = null; if (allSpecs[i].LocalPath != null) path = Utils.LocalPathToAssetPath(allSpecs[i].LocalPath.Path); else if (allSpecs[i].ClientPath != null) path = Utils.LocalPathToAssetPath(Utils.ClientPathToLocalPath(allSpecs[i].ClientPath.Path)); else if (allSpecs[i].DepotPath != null) path = Utils.LocalPathToAssetPath(Utils.DepotPathToLocalPath(aConnection, allSpecs[i].DepotPath)); #if DEBUG log.Debug("_Statuses[" + Logger.ToStringNullSafe(path) + "] = " + allStatuses[i].LocalState.ToString()); #endif if (path != null) { _Statuses[GetEffectivePath(path)] = allStatuses[i]; } } if (_OnAssetStatusChanged != null) { _OnAssetStatusChanged(aConnection); } } } static void GetAssetData(PerforceConnection aConnection, List aFiles, List aOutStatuses) { DateTime startTimestamp = DateTime.Now; if (aFiles.Count > 0) { Revision invalid = new Revision(-1); List validSpecs = new List(); for (int i = 0; i < aFiles.Count; ++i) { FileSpec spec = aFiles[i]; if (!Version.Equals(spec.Version, invalid)) { validSpecs.Add(spec); } } if (validSpecs.Count != 0) { IList allMetas = Engine.GetFileMetaData(aConnection, validSpecs, null); List matchingMetas = new List(); DateTime matchingMetaStartTime = DateTime.Now; Utils.GetMatchingMetaData(aFiles, allMetas, matchingMetas); if (Config.DisplayP4Timings) { double deltaInnerTime = (DateTime.Now - matchingMetaStartTime).TotalMilliseconds; aConnection.AppendTimingInfo("GetMatchingMetaData " + deltaInnerTime.ToString() + " ms"); } for (int i = 0; i < aFiles.Count; ++i) { if (matchingMetas[i] != null) { aOutStatuses.Add(CreateAssetStatus(matchingMetas[i])); } else { aOutStatuses.Add(new AssetStatus()); } } } else { for (int i = 0; i < aFiles.Count; ++i) { aOutStatuses.Add(new AssetStatus()); } } } if (Config.DisplayP4Timings) { double deltaInnerTime = (DateTime.Now - startTimestamp).TotalMilliseconds; aConnection.AppendTimingInfo("GetAssetData " + deltaInnerTime.ToString() + " ms"); } } /// /// Returns a proper list of file spec given a list of paths, accounting for directories /// static List GetFileSpecs(List aAssetPaths) { List localFiles = new List(); foreach (var path in aAssetPaths) { localFiles.Add(FileSpec.LocalSpec(Utils.AssetPathToLocalPath(GetEffectivePath(path)))); } return localFiles; } /// /// Returns the path to use when printing the name, or looking up status info /// static string GetEffectivePath(string aAssetPath) { if (Utils.IsDirectory(aAssetPath)) { return Utils.MetaFromAsset(aAssetPath); } else { return aAssetPath; } } } }