using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Threading; using Codice; using Codice.Client.BaseCommands; using Codice.Client.Commands; using Codice.Client.Commands.WkTree; using Codice.LogWrapper; using Codice.Utils; using GluonGui; using PlasticGui; using PlasticGui.WorkspaceWindow; using Unity.PlasticSCM.Editor.UI; using Unity.PlasticSCM.Editor.Views.Merge; using Unity.PlasticSCM.Editor.Views.PendingChanges; namespace Unity.PlasticSCM.Editor.AssetUtils.Processor { internal class WorkspaceOperationsMonitor { // Internal usage. This isn't a public API. [EditorBrowsable(EditorBrowsableState.Never)] public interface IDisableAssetsProcessor { void Disable(); } internal WorkspaceOperationsMonitor( IPlasticAPI plasticApi, IDisableAssetsProcessor disableAssetsProcessor, bool isGluonMode) { mPlasticAPI = plasticApi; mDisableAssetsProcessor = disableAssetsProcessor; mIsGluonMode = isGluonMode; } internal void RegisterWindow( IWorkspaceWindow workspaceWindow, ViewHost viewHost, NewIncomingChangesUpdater incomingChangesUpdater) { mWorkspaceWindow = workspaceWindow; mViewHost = viewHost; mNewIncomingChangesUpdater = incomingChangesUpdater; } internal void UnRegisterWindow() { mWorkspaceWindow = null; mViewHost = null; mNewIncomingChangesUpdater = null; } internal void RegisterPendingChangesView( PendingChangesTab pendingChangesTab) { mPendingChangesTab = pendingChangesTab; } internal void RegisterIncomingChangesView( IIncomingChangesTab incomingChangesTab) { mIncomingChangesTab = incomingChangesTab; } internal void UnRegisterViews() { mPendingChangesTab = null; mIncomingChangesTab = null; } internal void Start() { mLog.Debug("Start"); mCancelToken = new CancelToken(); mIsEnabled = true; Thread thread = new Thread(TaskLoopThread); thread.IsBackground = true; thread.Name = "Plastic SCM Workspace Operations Monitor thread"; thread.Start(); } internal void Stop() { mLog.Debug("Stop"); SetAsFinished(); } internal void Disable() { mIsEnabled = false; mLog.Debug("Disabled"); } internal void Enable() { mIsEnabled = true; mLog.Debug("Enabled"); } internal void AddAssetsProcessorPathsToAdd( List paths) { AddPathsToProcess( mAssetsProcessorPathsToAdd, paths, mLock, mResetEvent, mIsEnabled); } internal void AddAssetsProcessorPathsToDelete( List paths) { AddPathsToProcess( mAssetsProcessorPathsToDelete, paths, mLock, mResetEvent, mIsEnabled); } internal void AddAssetsProcessorPathsToCheckout( List paths) { AddPathsToProcess( mAssetsProcessorPathsToCheckout, paths, mLock, mResetEvent, mIsEnabled); } internal void AddAssetsProcessorPathsToMove( List paths) { AddPathsToMoveToProcess( mAssetsProcessorPathsToMove, paths, mLock, mResetEvent, mIsEnabled); } internal void AddPathsToCheckout( List paths) { AddPathsToProcess( mPathsToCheckout, paths, mLock, mResetEvent, mIsEnabled); } void TaskLoopThread() { while (true) { try { if (mCancelToken.IsCancelled()) break; if (!mIsEnabled) { SleepUntilNextWorkload(); continue; } bool hasAssetProcessorOpsPending = false; bool hasCheckoutOpsPending = false; ProcessOperations( mPlasticAPI, mAssetsProcessorPathsToAdd, mAssetsProcessorPathsToDelete, mAssetsProcessorPathsToCheckout, mAssetsProcessorPathsToMove, mPathsToCheckout, mCancelToken, mLock, mDisableAssetsProcessor, out hasAssetProcessorOpsPending, out hasCheckoutOpsPending); if (mCancelToken.IsCancelled()) break; if (hasAssetProcessorOpsPending || hasCheckoutOpsPending) continue; SleepUntilNextWorkload(); } catch (Exception e) { mLog.ErrorFormat( "Error running the tasks loop : {0}", e.Message); mLog.DebugFormat( "Stacktrace: {0}", e.StackTrace); } } } void ProcessOperations( IPlasticAPI plasticApi, List assetsProcessorPathsToAdd, List assetsProcessorPathsToDelete, List assetsProcessorPathsToCheckout, List assetsProcessorPathsToMove, List pathsToCheckout, CancelToken cancelToken, object lockObj, IDisableAssetsProcessor disableAssetsProcessor, out bool hasAssetProcessorOpsPending, out bool hasCheckoutOpsPending) { hasAssetProcessorOpsPending = false; hasCheckoutOpsPending = false; mLog.Debug("Starting process operations..."); bool hasAssetProcessorOpsProcessed = ProcessAssetProcessorOperations( plasticApi, assetsProcessorPathsToAdd, assetsProcessorPathsToDelete, assetsProcessorPathsToCheckout, assetsProcessorPathsToMove, cancelToken, lockObj, disableAssetsProcessor); if (cancelToken.IsCancelled()) return; bool hasCheckoutOpsProcessed = ProcessCheckoutOperation( plasticApi, pathsToCheckout, cancelToken, lockObj); mLog.Debug("ProcessOperations - Processed paths ? " + (hasAssetProcessorOpsProcessed || hasCheckoutOpsProcessed)); if (cancelToken.IsCancelled()) return; HasPendingOperationsToProcess( assetsProcessorPathsToAdd, assetsProcessorPathsToDelete, assetsProcessorPathsToCheckout, assetsProcessorPathsToMove, pathsToCheckout, lockObj, out hasAssetProcessorOpsPending, out hasCheckoutOpsPending); bool isAfterAssetProcessorOpNeeded = hasAssetProcessorOpsProcessed && !hasAssetProcessorOpsPending; bool isAfterCheckoutOpNeeded = hasCheckoutOpsProcessed && !hasCheckoutOpsPending; if (!isAfterAssetProcessorOpNeeded && !isAfterCheckoutOpNeeded) return; if (cancelToken.IsCancelled()) return; EditorDispatcher.Dispatch(() => { mLog.Debug("AfterProcessOperations"); RefreshAsset.VersionControlCache(); if (isAfterAssetProcessorOpNeeded) AfterAssetProcessorOperation(); if (isAfterCheckoutOpNeeded) AfterCheckoutOperation(); }); } void AfterAssetProcessorOperation() { AutoRefresh.PendingChangesView(mPendingChangesTab); AutoRefresh.IncomingChangesView(mIncomingChangesTab); if (mIsGluonMode) { RefreshViewsAfterAssetProcessorForGluon(mViewHost); return; } RefreshViewsAfterAssetProcessorForDeveloper(mWorkspaceWindow); } void AfterCheckoutOperation() { if (mIsGluonMode) { RefreshViewsAfterCheckoutForGluon(mViewHost); return; } if (mNewIncomingChangesUpdater != null) mNewIncomingChangesUpdater.Update(DateTime.Now); RefreshViewsAfterCheckoutForDeveloper(mWorkspaceWindow); } void SetAsFinished() { if (mCancelToken.IsCancelled()) return; mCancelToken.Cancel(); mResetEvent.Set(); } void SleepUntilNextWorkload() { mLog.Debug("SleepUntilNextWorkload"); mResetEvent.Reset(); mResetEvent.WaitOne(); } static bool ProcessAssetProcessorOperations( IPlasticAPI plasticApi, List assetsProcessorPathsToAdd, List assetsProcessorPathsToDelete, List assetsProcessorPathsToCheckout, List assetsProcessorPathsToMove, CancelToken cancelToken, object lockObj, IDisableAssetsProcessor disableAssetsProcessor) { bool hasProcessedPaths = false; try { hasProcessedPaths = AssetsProcessorOperations. AddIfNotControlled( plasticApi, ExtractPathsToProcess(assetsProcessorPathsToAdd, lockObj), cancelToken); if (cancelToken.IsCancelled()) return false; hasProcessedPaths |= AssetsProcessorOperations. DeleteIfControlled( plasticApi, ExtractPathsToProcess(assetsProcessorPathsToDelete, lockObj), cancelToken); if (cancelToken.IsCancelled()) return false; hasProcessedPaths |= AssetsProcessorOperations. MoveIfControlled( plasticApi, ExtractPathsToMoveToProcess(assetsProcessorPathsToMove, lockObj).AsReadOnly(), cancelToken); if (cancelToken.IsCancelled()) return false; hasProcessedPaths |= AssetsProcessorOperations. CheckoutIfControlledAndChanged( plasticApi, ExtractPathsToProcess(assetsProcessorPathsToCheckout, lockObj), cancelToken); } catch (Exception ex) { LogException(ex); disableAssetsProcessor.Disable(); } return hasProcessedPaths; } static bool ProcessCheckoutOperation( IPlasticAPI plasticApi, List pathsToProcess, CancelToken cancelToken, object lockObj) { List paths = ExtractPathsToProcess( pathsToProcess, lockObj); List result = new List(); foreach (string path in paths) { if (cancelToken.IsCancelled()) return false; WorkspaceTreeNode node = plasticApi.GetWorkspaceTreeNode(path); if (node != null && !CheckWorkspaceTreeNodeStatus.IsCheckedOut(node)) result.Add(path); } if (cancelToken.IsCancelled()) return false; bool hasPathsToProcess = result.Count > 0; if (hasPathsToProcess) { plasticApi.Checkout( result.ToArray(), CheckoutModifiers.ProcessSymlinks); } LogProcessedPaths("ProcessCheckoutOperation", result); return hasPathsToProcess; } static void AddPathsToProcess( List pathsToProcess, List paths, object lockObj, ManualResetEvent resetEvent, bool isEnabled) { if (!isEnabled) return; lock (lockObj) { pathsToProcess.AddRange(paths); } resetEvent.Set(); } static void AddPathsToMoveToProcess( List pathsToProcess, List paths, object lockObj, ManualResetEvent resetEvent, bool isEnabled) { if (!isEnabled) return; lock (lockObj) { pathsToProcess.AddRange(paths); } resetEvent.Set(); } static List ExtractPathsToProcess( List pathsToProcess, object lockObj) { List result; lock (lockObj) { result = new List(pathsToProcess); pathsToProcess.Clear(); } return result; } static List ExtractPathsToMoveToProcess( List pathsToProcess, object lockObj) { List result; lock (lockObj) { result = new List(pathsToProcess); pathsToProcess.Clear(); } return result; } static void HasPendingOperationsToProcess( List assetsProcessorPathsToAdd, List assetsProcessorPathsToDelete, List assetsProcessorPathsToCheckout, List assetsProcessorPathsToMove, List pathsToCheckout, object lockObj, out bool hasAssetProcessorOperations, out bool hasCheckoutOperations) { lock (lockObj) { hasAssetProcessorOperations = assetsProcessorPathsToAdd.Count > 0 || assetsProcessorPathsToDelete.Count > 0 || assetsProcessorPathsToCheckout.Count > 0 || assetsProcessorPathsToMove.Count > 0; hasCheckoutOperations = pathsToCheckout.Count > 0; } } static void RefreshViewsAfterAssetProcessorForGluon(ViewHost viewHost) { if (viewHost == null) { return; } viewHost.RefreshView(ViewType.LocksView); } static void RefreshViewsAfterAssetProcessorForDeveloper(IWorkspaceWindow workspaceWindow) { if (workspaceWindow == null) { return; } workspaceWindow.RefreshView(ViewType.LocksView); } static void RefreshViewsAfterCheckoutForDeveloper( IWorkspaceWindow workspaceWindow) { if (workspaceWindow == null) return; workspaceWindow.RefreshView(ViewType.BranchExplorerView); workspaceWindow.RefreshView(ViewType.PendingChangesView); workspaceWindow.RefreshView(ViewType.HistoryView); workspaceWindow.RefreshView(ViewType.LocksView); } static void RefreshViewsAfterCheckoutForGluon( ViewHost viewHost) { if (viewHost == null) return; viewHost.RefreshView(ViewType.WorkspaceExplorerView); viewHost.RefreshView(ViewType.CheckinView); viewHost.RefreshView(ViewType.IncomingChangesView); viewHost.RefreshView(ViewType.SearchView); viewHost.RefreshView(ViewType.LocksView); } static void LogProcessedPaths( string operation, List paths) { if (paths.Count == 0) { mLog.DebugFormat( "{0} - There are no processed paths.", operation); return; } mLog.DebugFormat( "{0} - Processed paths: {1}{2}", operation, Environment.NewLine, string.Join(Environment.NewLine, paths)); } static void LogException(Exception ex) { mLog.WarnFormat("Message: {0}", ex.Message); mLog.DebugFormat( "StackTrace:{0}{1}", Environment.NewLine, ex.StackTrace); } internal static class AssetsProcessorOperations { internal static bool AddIfNotControlled( IPlasticAPI plasticApi, List paths, CancelToken cancelToken) { List result = new List(); IgnoredFilesFilter ignoredFilter = plasticApi.GetIgnoredFilter(); foreach (string path in paths) { if (cancelToken.IsCancelled()) return false; string metaPath = MetaPath.GetMetaPath(path); if (plasticApi.GetWorkspaceFromPath(path) == null) return false; if (plasticApi.GetWorkspaceTreeNode(path) == null && !ignoredFilter.IsIgnored(path)) { result.Add(path); } if (File.Exists(metaPath) && plasticApi.GetWorkspaceTreeNode(metaPath) == null && !ignoredFilter.IsIgnored(path)) { result.Add(metaPath); } } if (cancelToken.IsCancelled()) return false; bool hasPathsToProcess = result.Count > 0; if (hasPathsToProcess) { IList checkouts; plasticApi.Add(result.ToArray(), GetDefaultAddOptions(), out checkouts); } LogProcessedPaths("AddIfNotControlled", result); return hasPathsToProcess; } internal static bool DeleteIfControlled( IPlasticAPI plasticApi, List paths, CancelToken cancelToken) { List processedPaths = new List(paths.Count); foreach (string path in paths) { if (cancelToken.IsCancelled()) return false; string metaPath = MetaPath.GetMetaPath(path); if (plasticApi.GetWorkspaceTreeNode(path) != null) { processedPaths.Add(path); } if (plasticApi.GetWorkspaceTreeNode(metaPath) != null) { processedPaths.Add(metaPath); } } if (cancelToken.IsCancelled()) return false; plasticApi.DeleteControlled( processedPaths.ToArray(), DeleteModifiers.None, null); LogProcessedPaths("DeleteIfControlled", processedPaths); return processedPaths.Count > 0; } internal static bool MoveIfControlled( IPlasticAPI plasticApi, ReadOnlyCollection paths, CancelToken cancelToken) { List processedPaths = new List(paths.Count); foreach (AssetPostprocessor.PathToMove pathToMove in paths) { if (cancelToken.IsCancelled()) return false; if (plasticApi.GetWorkspaceTreeNode(pathToMove.SrcPath) != null) { plasticApi.Move( pathToMove.SrcPath, pathToMove.DstPath, MoveModifiers.None); processedPaths.Add(string.Format("{0} to {1}", pathToMove.SrcPath, pathToMove.DstPath)); } if (cancelToken.IsCancelled()) return false; string srcMetaPath = MetaPath.GetMetaPath(pathToMove.SrcPath); string dstMetaPath = MetaPath.GetMetaPath(pathToMove.DstPath); if (plasticApi.GetWorkspaceTreeNode(srcMetaPath) != null) { plasticApi.Move( srcMetaPath, dstMetaPath, MoveModifiers.None); processedPaths.Add(string.Format("{0} to {1}", srcMetaPath, dstMetaPath)); } } LogProcessedPaths("MoveIfControlled", processedPaths); return processedPaths.Count > 0; } internal static bool CheckoutIfControlledAndChanged( IPlasticAPI plasticApi, List paths, CancelToken cancelToken) { List result = new List(); HiddenChangesFilesFilter hiddenChangesFilter = plasticApi.GetHiddenChangesFilter(); foreach (string path in paths) { if (cancelToken.IsCancelled()) return false; string metaPath = MetaPath.GetMetaPath(path); WorkspaceTreeNode node = plasticApi.GetWorkspaceTreeNode(path); WorkspaceTreeNode nodeMeta = plasticApi.GetWorkspaceTreeNode(metaPath); if (node != null && !CheckWorkspaceTreeNodeStatus.IsCheckedOut(node) && !hiddenChangesFilter.IsHiddenChanged(path) && ChangedFileChecker.IsChanged(node.LocalInfo, path, false)) { result.Add(path); } if (nodeMeta != null && !CheckWorkspaceTreeNodeStatus.IsCheckedOut(nodeMeta) && !hiddenChangesFilter.IsHiddenChanged(path) && ChangedFileChecker.IsChanged(nodeMeta.LocalInfo, metaPath, false)) { result.Add(metaPath); } } if (cancelToken.IsCancelled()) return false; bool hasPathsToProcess = result.Count > 0; if (hasPathsToProcess) { plasticApi.Checkout(result.ToArray(), CheckoutModifiers.None); } LogProcessedPaths("CheckoutIfControlledAndChanged", result); return hasPathsToProcess; } static AddOptions GetDefaultAddOptions() { AddOptions options = new AddOptions(); options.AddPrivateParents = true; options.NeedCheckPlatformPath = true; return options; } } object mLock = new object(); List mAssetsProcessorPathsToAdd = new List(); List mAssetsProcessorPathsToDelete = new List(); List mAssetsProcessorPathsToCheckout = new List(); List mAssetsProcessorPathsToMove = new List(); List mPathsToCheckout = new List(); PendingChangesTab mPendingChangesTab; IIncomingChangesTab mIncomingChangesTab; IWorkspaceWindow mWorkspaceWindow; ViewHost mViewHost; NewIncomingChangesUpdater mNewIncomingChangesUpdater; volatile bool mIsEnabled; volatile ManualResetEvent mResetEvent = new ManualResetEvent(false); CancelToken mCancelToken = new CancelToken(); readonly bool mIsGluonMode = false; readonly IDisableAssetsProcessor mDisableAssetsProcessor; readonly IPlasticAPI mPlasticAPI; static readonly ILog mLog = PlasticApp.GetLogger("WorkspaceOperationsMonitor"); } }