internal async Task BeginGetGraphDataAsync(IGraphContext context) { try { await EnsureInitializedAsync().ConfigureAwait(false); var actionHandlers = GraphActionHandlers.Where(x => x.Value.CanHandleRequest(context)); var shouldTrackChanges = actionHandlers.Aggregate( false, (previousTrackFlag, handler) => previousTrackFlag || handler.Value.HandleRequest(context)); lock (_ExpandedGraphContextsLock) { if (shouldTrackChanges && !ExpandedGraphContexts.Contains(context)) { // Remember this graph context in order to track changes. // When references change, we will adjust children of this graph as necessary ExpandedGraphContexts.Add(context); } } } finally { // OnCompleted must be called to display changes context.OnCompleted(); } }
/// <summary> /// Property ExpandedGraphContexts remembers graph expanded so far. /// Each context represents one level in the graph, i.e. a node and its first level dependencies /// Tracking changes over all expanded contexts ensures that all levels are processed /// and updated when there are any changes in nodes data. /// </summary> internal async Task TrackChangesAsync(IDependenciesGraphProjectContext updatedProjectContext) { foreach (var graphContext in ExpandedGraphContexts.ToList()) { try { await TrackChangesOnGraphContextAsync(graphContext, updatedProjectContext).ConfigureAwait(false); } finally { // Calling OnCompleted ensures that the changes are reflected in UI graphContext.OnCompleted(); } } }
/// <summary> /// Property ExpandedGraphContexts remembers graph expanded or checked so far. /// Each context represents one level in the graph, i.e. a node and its first level dependencies /// Tracking changes over all expanded contexts ensures that all levels are processed /// and updated when there are any changes in nodes data. /// </summary> private void TrackChanges(SnapshotChangedEventArgs updatedProjectContext) { IList <IGraphContext> expandedContexts; lock (_expandedGraphContextsLock) { expandedContexts = ExpandedGraphContexts.ToList(); } if (expandedContexts.Count == 0) { return; } var actionHandlers = GraphActionHandlers.Select(x => x.Value).Where(x => x.CanHandleChanges()).ToList(); if (actionHandlers.Count == 0) { return; } foreach (IGraphContext graphContext in expandedContexts) { try { foreach (IDependenciesGraphActionHandler actionHandler in actionHandlers) { actionHandler.HandleChanges(graphContext, updatedProjectContext); } } finally { // Calling OnCompleted ensures that the changes are reflected in UI graphContext.OnCompleted(); } } }
/// <summary> /// Property ExpandedGraphContexts remembers graph expanded or checked so far. /// Each context represents one level in the graph, i.e. a node and its first level dependencies /// Tracking changes over all expanded contexts ensures that all levels are processed /// and updated when there are any changes in nodes data. /// </summary> internal Task TrackChangesAsync(SnapshotChangedEventArgs updatedProjectContext) { IList <IGraphContext> expandedContexts; lock (_expandedGraphContextsLock) { expandedContexts = ExpandedGraphContexts.ToList(); } if (expandedContexts.Count == 0) { return(Task.CompletedTask); } var actionHandlers = GraphActionHandlers.Where(x => x.Value.CanHandleChanges()); if (!actionHandlers.Any()) { return(Task.CompletedTask); } foreach (var graphContext in expandedContexts.ToList()) { try { actionHandlers.ForEach(x => x.Value.HandleChanges(graphContext, updatedProjectContext)); } finally { // Calling OnCompleted ensures that the changes are reflected in UI graphContext.OnCompleted(); } } return(Task.CompletedTask); }
/// <summary> /// Fills node with children when it is opened in Solution Explorer /// </summary> private async Task GetChildrenAsync(IGraphContext graphContext) { var trackChanges = false; foreach (var inputGraphNode in graphContext.InputNodes) { if (graphContext.CancelToken.IsCancellationRequested) { return; } var projectPath = GetPartialValueFromGraphNodeId(inputGraphNode.Id, CodeGraphNodeIdName.Assembly); if (string.IsNullOrEmpty(projectPath)) { continue; } var(node, subTreeProvider) = await GetDependencyNodeInfoAsync(graphContext, inputGraphNode, projectPath) .ConfigureAwait(false); if (node == null || subTreeProvider == null) { continue; } if (!node.Flags.Contains(DependencyNode.PreFilledFolderNode)) { // Refresh reference, projectContext may have been changed since the last time CheckChildren was called node = subTreeProvider.GetDependencyNode(node.Id); if (node == null) { continue; } } var nodeChildren = node.Children.ToArray(); if (!string.IsNullOrEmpty(node.Id.ContextProject)) { projectPath = node.Id.ContextProject; } // get specific providers for child nodes outside of GraphTransactionScope since it does not support // await and switch to other thread (exception "scope must be completed by the same thread it is created". var childrenSubTreeProviders = new List <IProjectDependenciesSubTreeProvider>(); foreach (var childNodeToAdd in nodeChildren) { var childSubTreeProvider = await GetSubTreeProviderAsync(graphContext, null /* inputGraphNode */, projectPath, childNodeToAdd.Id).ConfigureAwait(false); childrenSubTreeProviders.Add(childSubTreeProvider); } using (var scope = new GraphTransactionScope()) { for (int i = 0; i < nodeChildren.Length; ++i) { var childNodeToAdd = nodeChildren[i]; var childSubTreeProvider = childrenSubTreeProviders?[i] ?? subTreeProvider; // start tracking changes if needed if (graphContext.TrackChanges) { trackChanges = true; } inputGraphNode.SetValue(DependenciesGraphSchema.DependencyNodeProperty, node); var newGraphNode = AddGraphNode(graphContext, projectPath, childSubTreeProvider, inputGraphNode, childNodeToAdd); if (childNodeToAdd.Flags.Contains(DependencyNode.PreFilledFolderNode)) { newGraphNode.SetValue(DgmlNodeProperties.ContainsChildren, true); } } scope.Complete(); } lock (_ExpandedGraphContextsLock) { if (trackChanges && !ExpandedGraphContexts.Contains(graphContext)) { // Remember this graph context in order to track changes. // When references change, we will adjust children of this graph as necessary ExpandedGraphContexts.Add(graphContext); } } } }
/// <summary> /// Fills node with children when it is opened in Solution Explorer /// </summary> private async Task GetChildrenAsync(IGraphContext graphContext) { var trackChanges = false; foreach (var inputGraphNode in graphContext.InputNodes) { if (graphContext.CancelToken.IsCancellationRequested) { return; } var projectPath = GetPartialValueFromGraphNodeId(inputGraphNode.Id, CodeGraphNodeIdName.Assembly); if (string.IsNullOrEmpty(projectPath)) { continue; } var nodeInfo = inputGraphNode.GetValue <IDependencyNode>( DependenciesGraphSchema.DependencyNodeProperty); if (nodeInfo == null) { continue; } var subTreeProvider = await GetSubTreeProviderAsync(graphContext, inputGraphNode, projectPath, nodeInfo.Id).ConfigureAwait(false); if (subTreeProvider == null) { continue; } if (!nodeInfo.Flags.Contains(DependencyNode.PreFilledFolderNode)) { // Refresh reference, projectContext may have been changed since the last time CheckChildren was called nodeInfo = subTreeProvider.GetDependencyNode(nodeInfo.Id); if (nodeInfo == null) { continue; } } using (var scope = new GraphTransactionScope()) { var nodeChildren = nodeInfo.Children; foreach (var childNodeToAdd in nodeChildren) { // start tracking changes if needed if (graphContext.TrackChanges) { trackChanges = true; } inputGraphNode.SetValue(DependenciesGraphSchema.DependencyNodeProperty, nodeInfo); var newGraphNode = AddGraphNode(graphContext, projectPath, subTreeProvider, inputGraphNode, childNodeToAdd); if (childNodeToAdd.Flags.Contains(DependencyNode.PreFilledFolderNode)) { newGraphNode.SetValue(DgmlNodeProperties.ContainsChildren, true); } } scope.Complete(); } lock (_ExpandedGraphContextsLock) { if (trackChanges && !ExpandedGraphContexts.Contains(graphContext)) { // Remember this graph context in order to track changes. // When references change, we will adjust children of this graph as necessary ExpandedGraphContexts.Add(graphContext); } } } }
/// <summary> /// Checks if given node has children and adds corresponding IDependencyDescription to the node. /// </summary> private async Task CheckChildrenAsync(IGraphContext graphContext) { foreach (var inputGraphNode in graphContext.InputNodes) { if (graphContext.CancelToken.IsCancellationRequested) { return; } var projectPath = GetPartialValueFromGraphNodeId(inputGraphNode.Id, CodeGraphNodeIdName.Assembly); if (string.IsNullOrEmpty(projectPath)) { continue; } var(node, subTreeProvider) = await GetDependencyNodeInfoAsync(graphContext, inputGraphNode, projectPath) .ConfigureAwait(false); if (node == null || subTreeProvider == null) { continue; } // refresh node node = subTreeProvider.GetDependencyNode(node.Id); if (node == null) { continue; } var trackChanges = false; using (var scope = new GraphTransactionScope()) { if (node.Flags.Contains(DependencyNode.DependsOnOtherProviders) || subTreeProvider.ProviderType.Equals(SdkDependenciesSubTreeProvider.ProviderTypeString, StringComparison.OrdinalIgnoreCase)) { trackChanges = true; } inputGraphNode.SetValue(DependenciesGraphSchema.ProviderProperty, subTreeProvider); inputGraphNode.SetValue(DependenciesGraphSchema.DependencyNodeProperty, node); if (node.HasChildren) { inputGraphNode.SetValue(DgmlNodeProperties.ContainsChildren, true); } scope.Complete(); } lock (_ExpandedGraphContextsLock) { if (trackChanges && !ExpandedGraphContexts.Contains(graphContext)) { // Remember this graph context in order to track changes. // When references change, we will adjust children of this graph as necessary ExpandedGraphContexts.Add(graphContext); } } } }