using System; using System.Collections.Generic; using System.IO; using Codice; using Codice.Client.Commands; using Codice.Client.Commands.Mount; using Codice.Client.Commands.WkTree; using Codice.Client.Common; using Codice.Client.Common.Locks; using Codice.Client.Common.Threading; using Codice.Client.Common.WkTree; using Codice.CM.Common; using Codice.Utils; using PlasticGui.WorkspaceWindow; namespace Unity.PlasticSCM.Editor.AssetsOverlays.Cache { internal class LockStatusCache { internal LockStatusCache( WorkspaceInfo wkInfo, Action repaintProjectWindow, Action repaintInspector) { mWkInfo = wkInfo; mRepaintProjectWindow = repaintProjectWindow; mRepaintInspector = repaintInspector; } internal AssetStatus GetStatus(string fullPath) { LockStatusData lockStatusData = GetLockStatusData(fullPath); if (lockStatusData == null) return AssetStatus.None; return lockStatusData.Status; } internal LockStatusData GetLockStatusData(string fullPath) { lock (mLock) { if (mStatusByPathCache == null) { mStatusByPathCache = BuildPathDictionary.ForPlatform(); mCurrentCancelToken.Cancel(); mCurrentCancelToken = new CancelToken(); AsyncCalculateStatus(mCurrentCancelToken); return null; } LockStatusData result; if (mStatusByPathCache.TryGetValue(fullPath, out result)) return result; return null; } } internal void Clear() { lock (mLock) { mCurrentCancelToken.Cancel(); mStatusByPathCache = null; } } void AsyncCalculateStatus(CancelToken cancelToken) { Dictionary statusByPathCache = null; IThreadWaiter waiter = ThreadWaiter.GetWaiter(50); waiter.Execute( /*threadOperationDelegate*/ delegate { Dictionary> lockCandidates = new Dictionary>(); FillLockCandidates.ForTree(mWkInfo, lockCandidates); if (cancelToken.IsCancelled()) return; Dictionary lockInfoByNode = SearchLocks.GetLocksInfo(mWkInfo, lockCandidates); if (cancelToken.IsCancelled()) return; statusByPathCache = BuildStatusByNodeCache. ForLocks(mWkInfo.ClientPath, lockInfoByNode); }, /*afterOperationDelegate*/ delegate { if (waiter.Exception != null) { ExceptionsHandler.LogException( "LockStatusCache", waiter.Exception); return; } if (cancelToken.IsCancelled()) return; lock (mLock) { mStatusByPathCache = statusByPathCache; } mRepaintProjectWindow(); mRepaintInspector(); }); } static class FillLockCandidates { internal static void ForTree( WorkspaceInfo wkInfo, Dictionary> lockCandidates) { WorkspaceTreeNode rootNode = CmConnection.Get().GetWorkspaceTreeHandler(). GetWorkspaceTree(wkInfo, wkInfo.ClientPath, true); Queue pendingDirectories = new Queue(); pendingDirectories.Enqueue(new NodeWithPath( MountPointWithPath.BuildWorkspaceRootMountPoint(rootNode.RepSpec), rootNode, wkInfo.ClientPath)); while (pendingDirectories.Count > 0) { NodeWithPath directoryNode = pendingDirectories.Dequeue(); ForChildren( wkInfo.ClientPath, directoryNode.Mount, directoryNode.Path, directoryNode.Node, pendingDirectories, lockCandidates); } } static void ForChildren( string wkPath, MountPointWithPath parentMount, string dirPath, WorkspaceTreeNode dirNode, Queue pendingDirectories, Dictionary> lockCandidates) { if (!dirNode.HasChildren) return; foreach (WorkspaceTreeNode child in dirNode.Children) { string childPath = Path.Combine(dirPath, child.Name); if (CheckWorkspaceTreeNodeStatus.IsDirectory(child)) { MountPointWithPath mount = XlinkWorkspaceTreeNode.IsXlinkWkNode(child) ? new MountPointWithPath( MountPointId.BuildForXlink( ((XlinkWorkspaceTreeNode)child).Xlink.GUID, parentMount.Id), child.RepSpec, WorkspacePath.CmPathFromWorkspacePath(childPath, wkPath)) : parentMount; pendingDirectories.Enqueue( new NodeWithPath(mount, child, childPath)); continue; } if (CheckWorkspaceTreeNodeStatus.IsAdded(child)) continue; List nodes = null; if (!lockCandidates.TryGetValue(parentMount, out nodes)) { nodes = new List(); lockCandidates.Add(parentMount, nodes); } nodes.Add(child); } } class NodeWithPath { internal readonly MountPointWithPath Mount; internal readonly WorkspaceTreeNode Node; internal readonly string Path; internal NodeWithPath( MountPointWithPath mount, WorkspaceTreeNode node, string path) { Mount = mount; Node = node; Path = path; } } } static class BuildStatusByNodeCache { internal static Dictionary ForLocks( string wkPath, Dictionary lockInfoByNode) { Dictionary result = BuildPathDictionary.ForPlatform(); LockOwnerNameResolver nameResolver = new LockOwnerNameResolver(); foreach (WorkspaceTreeNode node in lockInfoByNode.Keys) { LockStatusData lockStatusData = BuildLockStatusData( node, lockInfoByNode[node], nameResolver); string nodeWkPath = WorkspacePath.GetWorkspacePathFromCmPath( wkPath, WorkspaceNodeOperations.GetCmPath(node), PathHelper.GetDirectorySeparatorChar(wkPath)); result.Add(nodeWkPath, lockStatusData); } return result; } static LockStatusData BuildLockStatusData( WorkspaceTreeNode node, LockInfo lockInfo, LockOwnerNameResolver nameResolver) { return new LockStatusData( GetAssetStatus(node, lockInfo), nameResolver.GetSeidName(lockInfo.SEIDData), BranchInfoCache.GetProtectedBranchName( node.RepSpec, lockInfo.HolderBranchId)); } static AssetStatus GetAssetStatus( WorkspaceTreeNode node, LockInfo lockInfo) { if (lockInfo.Status == LockInfo.LockStatus.Retained) return AssetStatus.Retained; return CheckWorkspaceTreeNodeStatus.IsCheckedOut(node) ? AssetStatus.Locked : AssetStatus.LockedRemote; } } CancelToken mCurrentCancelToken = new CancelToken(); Dictionary mStatusByPathCache; readonly Action mRepaintInspector; readonly Action mRepaintProjectWindow; readonly WorkspaceInfo mWkInfo; static object mLock = new object(); } }