From 8887a834fb0eb7a21046c60cd980101523488097 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 23 Jan 2018 17:13:38 +0100 Subject: [PATCH 1/4] Ensure that the GitHubPaneViewModel is initalized. - Renamed `IGitHubToolWindowManager.ShowHomePane` to `ShowGitHubPane` because the latter is what we call it these days - Make `IGitHubToolWindowManager.ShowGitHubPane` async to make sure that `GitHubPaneViewModel.InitializeAsync` has finished before passing back a refrence to the `GitHubPaneViewModel` - Make the methods that call `IGitHubToolWindowManager.ShowGitHubPane` `void async` with logging when they fail --- src/GitHub.VisualStudio/GitHubPackage.cs | 7 ++-- .../IServiceProviderPackage.cs | 3 +- .../Menus/OpenPullRequests.cs | 25 ++++++++----- .../Menus/ShowCurrentPullRequest.cs | 35 ++++++++++++------- .../Menus/ShowGitHubPane.cs | 2 +- src/GitHub.VisualStudio/UI/GitHubPane.cs | 1 - 6 files changed, 47 insertions(+), 26 deletions(-) diff --git a/src/GitHub.VisualStudio/GitHubPackage.cs b/src/GitHub.VisualStudio/GitHubPackage.cs index b04b53b1c5..f2135e4a3a 100644 --- a/src/GitHub.VisualStudio/GitHubPackage.cs +++ b/src/GitHub.VisualStudio/GitHubPackage.cs @@ -156,7 +156,7 @@ protected override Task InitializeAsync(CancellationToken cancellationToken, IPr return Task.CompletedTask; } - public IGitHubPaneViewModel ShowHomePane() + public async Task ShowGitHubPane() { var pane = ShowToolWindow(new Guid(GitHubPane.GitHubPaneGuid)); if (pane == null) @@ -166,7 +166,10 @@ public IGitHubPaneViewModel ShowHomePane() { ErrorHandler.Failed(frame.Show()); } - return (IGitHubPaneViewModel)((FrameworkElement)pane.Content).DataContext; + + var viewModel = (IGitHubPaneViewModel)((FrameworkElement)pane.Content).DataContext; + await viewModel.InitializeAsync(pane); + return viewModel; } static ToolWindowPane ShowToolWindow(Guid windowGuid) diff --git a/src/GitHub.VisualStudio/IServiceProviderPackage.cs b/src/GitHub.VisualStudio/IServiceProviderPackage.cs index 60b0da8b22..447301ca1f 100644 --- a/src/GitHub.VisualStudio/IServiceProviderPackage.cs +++ b/src/GitHub.VisualStudio/IServiceProviderPackage.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using System.Threading.Tasks; using GitHub.ViewModels.GitHubPane; namespace GitHub.VisualStudio @@ -12,6 +13,6 @@ public interface IServiceProviderPackage : IServiceProvider, Microsoft.VisualStu [ComVisible(true)] public interface IGitHubToolWindowManager { - IGitHubPaneViewModel ShowHomePane(); + Task ShowGitHubPane(); } } diff --git a/src/GitHub.VisualStudio/Menus/OpenPullRequests.cs b/src/GitHub.VisualStudio/Menus/OpenPullRequests.cs index f37f29f551..134be287ce 100644 --- a/src/GitHub.VisualStudio/Menus/OpenPullRequests.cs +++ b/src/GitHub.VisualStudio/Menus/OpenPullRequests.cs @@ -1,15 +1,17 @@ -using GitHub.Exports; -using GitHub.UI; -using GitHub.VisualStudio.UI; -using System; -using GitHub.Services; +using System; +using GitHub.Exports; using GitHub.Extensions; +using GitHub.Logging; +using GitHub.Services; +using Serilog; namespace GitHub.VisualStudio.Menus { [ExportMenu(MenuType = MenuType.OpenPullRequests)] public class OpenPullRequests : MenuBase, IMenuHandler { + static readonly ILogger log = LogManager.ForContext(); + public OpenPullRequests(IGitHubServiceProvider serviceProvider) : base(serviceProvider) { @@ -19,10 +21,17 @@ public OpenPullRequests(IGitHubServiceProvider serviceProvider) public Guid Guid => Guids.guidGitHubCmdSet; public int CmdId => PkgCmdIDList.openPullRequestsCommand; - public void Activate(object data = null) + public async void Activate(object data = null) { - var host = ServiceProvider.TryGetService().ShowHomePane(); - host.ShowPullRequests().Forget(); + try + { + var host = await ServiceProvider.TryGetService().ShowGitHubPane(); + await host.ShowPullRequests(); + } + catch (Exception ex) + { + log.Error(ex, "Error showing opening pull requests"); + } } } } diff --git a/src/GitHub.VisualStudio/Menus/ShowCurrentPullRequest.cs b/src/GitHub.VisualStudio/Menus/ShowCurrentPullRequest.cs index 267530efd5..fb825ff0a7 100644 --- a/src/GitHub.VisualStudio/Menus/ShowCurrentPullRequest.cs +++ b/src/GitHub.VisualStudio/Menus/ShowCurrentPullRequest.cs @@ -1,15 +1,17 @@ using System; using GitHub.Exports; -using GitHub.UI; -using GitHub.Services; using GitHub.Extensions; -using GitHub.Models; +using GitHub.Logging; +using GitHub.Services; +using Serilog; namespace GitHub.VisualStudio.Menus { [ExportMenu(MenuType = MenuType.OpenPullRequests)] public class ShowCurrentPullRequest : MenuBase, IMenuHandler { + static readonly ILogger log = LogManager.ForContext(); + public ShowCurrentPullRequest(IGitHubServiceProvider serviceProvider) : base(serviceProvider) { @@ -19,19 +21,26 @@ public ShowCurrentPullRequest(IGitHubServiceProvider serviceProvider) public Guid Guid => Guids.guidGitHubCmdSet; public int CmdId => PkgCmdIDList.showCurrentPullRequestCommand; - public void Activate(object data = null) + public async void Activate(object data = null) { - var pullRequestSessionManager = ServiceProvider.ExportProvider.GetExportedValueOrDefault(); - var session = pullRequestSessionManager?.CurrentSession; - if (session == null) + try { - return; // No active PR session. - } + var pullRequestSessionManager = ServiceProvider.ExportProvider.GetExportedValueOrDefault(); + var session = pullRequestSessionManager?.CurrentSession; + if (session == null) + { + return; // No active PR session. + } - var pullRequest = session.PullRequest; - var manager = ServiceProvider.TryGetService(); - var host = manager.ShowHomePane(); - host.ShowPullRequest(session.RepositoryOwner, host.LocalRepository.Name, pullRequest.Number); + var pullRequest = session.PullRequest; + var manager = ServiceProvider.TryGetService(); + var host = await manager.ShowGitHubPane(); + await host.ShowPullRequest(session.RepositoryOwner, host.LocalRepository.Name, pullRequest.Number); + } + catch (Exception ex) + { + log.Error(ex, "Error showing current pull request"); + } } } } diff --git a/src/GitHub.VisualStudio/Menus/ShowGitHubPane.cs b/src/GitHub.VisualStudio/Menus/ShowGitHubPane.cs index f693106d52..d967c471a1 100644 --- a/src/GitHub.VisualStudio/Menus/ShowGitHubPane.cs +++ b/src/GitHub.VisualStudio/Menus/ShowGitHubPane.cs @@ -17,7 +17,7 @@ public ShowGitHubPane(IGitHubServiceProvider serviceProvider) public void Activate(object data = null) { - ServiceProvider.TryGetService()?.ShowHomePane(); + ServiceProvider.TryGetService()?.ShowGitHubPane(); } } } diff --git a/src/GitHub.VisualStudio/UI/GitHubPane.cs b/src/GitHub.VisualStudio/UI/GitHubPane.cs index df89bc39b4..f2381f326c 100644 --- a/src/GitHub.VisualStudio/UI/GitHubPane.cs +++ b/src/GitHub.VisualStudio/UI/GitHubPane.cs @@ -89,7 +89,6 @@ public void Initialize(IServiceProvider serviceProvider) var factory = provider.GetService(); viewModel = provider.ExportProvider.GetExportedValue(); - viewModel.InitializeAsync(this).Forget(); View = factory.CreateView(); View.DataContext = viewModel; From ffd5072f09590a2b3f3629b1334c4298e077a80d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 7 Feb 2018 17:49:59 +0100 Subject: [PATCH 2/4] Ensure GitHubPane initializes view model is initialized. When the GitHub pane is shown from VS rather than our own code, kick off an initialization of the view model. This means we can't be sure of the state of the view model's initialization so make subsequent calls to the view model async either wait for an ongoing initialization, or exit if already initialized. --- .../GitHubPane/GitHubPaneViewModel.cs | 53 ++++++++++++------- src/GitHub.VisualStudio/UI/GitHubPane.cs | 1 + 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs index 1125b6c211..9dd364c0ac 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs @@ -46,6 +46,8 @@ public sealed class GitHubPaneViewModel : ViewModelBase, IGitHubPaneViewModel, I readonly ReactiveCommand refresh; readonly ReactiveCommand showPullRequests; readonly ReactiveCommand openInBrowser; + readonly SemaphoreSlim initializing = new SemaphoreSlim(1); + bool initialized; IViewModel content; ILocalRepositoryModel localRepository; string searchQuery; @@ -198,26 +200,37 @@ public void Dispose() /// public async Task InitializeAsync(IServiceProvider paneServiceProvider) { - await UpdateContent(teamExplorerContext.ActiveRepository); - teamExplorerContext.WhenAnyValue(x => x.ActiveRepository) - .Skip(1) - .ObserveOn(RxApp.MainThreadScheduler) - .Subscribe(x => UpdateContent(x).Forget()); - - connectionManager.Connections.CollectionChanged += (_, __) => UpdateContent(LocalRepository).Forget(); - - BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.pullRequestCommand, showPullRequests); - BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.backCommand, navigator.NavigateBack); - BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.forwardCommand, navigator.NavigateForward); - BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.refreshCommand, refresh); - BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.githubCommand, openInBrowser); - - paneServiceProvider.AddCommandHandler(Guids.guidGitHubToolbarCmdSet, PkgCmdIDList.helpCommand, - (_, __) => - { - browser.OpenUrl(new Uri(GitHubUrls.Documentation)); - usageTracker.IncrementCounter(x => x.NumberOfGitHubPaneHelpClicks).Forget(); - }); + await initializing.WaitAsync(); + if (initialized) return; + + try + { + await UpdateContent(teamExplorerContext.ActiveRepository); + teamExplorerContext.WhenAnyValue(x => x.ActiveRepository) + .Skip(1) + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(x => UpdateContent(x).Forget()); + + connectionManager.Connections.CollectionChanged += (_, __) => UpdateContent(LocalRepository).Forget(); + + BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.pullRequestCommand, showPullRequests); + BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.backCommand, navigator.NavigateBack); + BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.forwardCommand, navigator.NavigateForward); + BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.refreshCommand, refresh); + BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.githubCommand, openInBrowser); + + paneServiceProvider.AddCommandHandler(Guids.guidGitHubToolbarCmdSet, PkgCmdIDList.helpCommand, + (_, __) => + { + browser.OpenUrl(new Uri(GitHubUrls.Documentation)); + usageTracker.IncrementCounter(x => x.NumberOfGitHubPaneHelpClicks).Forget(); + }); + } + finally + { + initialized = true; + initializing.Release(); + } } /// diff --git a/src/GitHub.VisualStudio/UI/GitHubPane.cs b/src/GitHub.VisualStudio/UI/GitHubPane.cs index f2381f326c..df89bc39b4 100644 --- a/src/GitHub.VisualStudio/UI/GitHubPane.cs +++ b/src/GitHub.VisualStudio/UI/GitHubPane.cs @@ -89,6 +89,7 @@ public void Initialize(IServiceProvider serviceProvider) var factory = provider.GetService(); viewModel = provider.ExportProvider.GetExportedValue(); + viewModel.InitializeAsync(this).Forget(); View = factory.CreateView(); View.DataContext = viewModel; From 79d4d7d00ca140fcf2d4f3eb509cbd3cbcfff02c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 7 Feb 2018 17:50:27 +0100 Subject: [PATCH 3/4] Ensure the PR session manager is initialized. --- .../Services/IPullRequestSessionManager.cs | 12 ++++++++++++ .../Services/PullRequestSessionManager.cs | 7 +++++++ .../Menus/ShowCurrentPullRequest.cs | 2 ++ 3 files changed, 21 insertions(+) diff --git a/src/GitHub.Exports.Reactive/Services/IPullRequestSessionManager.cs b/src/GitHub.Exports.Reactive/Services/IPullRequestSessionManager.cs index 1069418a65..41c1a2f055 100644 --- a/src/GitHub.Exports.Reactive/Services/IPullRequestSessionManager.cs +++ b/src/GitHub.Exports.Reactive/Services/IPullRequestSessionManager.cs @@ -31,6 +31,18 @@ public interface IPullRequestSessionManager : INotifyPropertyChanged /// IPullRequestSession CurrentSession { get; } + /// + /// Ensures that the service is initialized. + /// + /// A task that when completed indicates that the service is initialized. + /// + /// If you are simplying monitoring changes to the then you + /// don't need to call this method: will be updated on + /// initialization. If however, you want to be sure that is + /// initialized, you can await the task returned from this method. + /// + Task EnsureInitialized(); + /// /// Gets an that tracks the live state of a document. /// diff --git a/src/GitHub.InlineReviews/Services/PullRequestSessionManager.cs b/src/GitHub.InlineReviews/Services/PullRequestSessionManager.cs index 9025893bc1..70975c5884 100644 --- a/src/GitHub.InlineReviews/Services/PullRequestSessionManager.cs +++ b/src/GitHub.InlineReviews/Services/PullRequestSessionManager.cs @@ -36,6 +36,7 @@ public class PullRequestSessionManager : ReactiveObject, IPullRequestSessionMana readonly IModelServiceFactory modelServiceFactory; readonly Dictionary, WeakReference> sessions = new Dictionary, WeakReference>(); + TaskCompletionSource initialized; IPullRequestSession currentSession; ILocalRepositoryModel repository; @@ -65,6 +66,7 @@ public PullRequestSessionManager( this.sessionService = sessionService; this.connectionManager = connectionManager; this.modelServiceFactory = modelServiceFactory; + initialized = new TaskCompletionSource(null); Observable.FromEventPattern(teamExplorerContext, nameof(teamExplorerContext.StatusChanged)) .ObserveOn(RxApp.MainThreadScheduler) @@ -82,6 +84,10 @@ public IPullRequestSession CurrentSession private set { this.RaiseAndSetIfChanged(ref currentSession, value); } } + /// + public Task EnsureInitialized() => initialized.Task; + + /// public async Task GetLiveFile( string relativePath, ITextView textView, @@ -230,6 +236,7 @@ async Task StatusChanged() } CurrentSession = session; + initialized.SetResult(null); } catch (Exception e) { diff --git a/src/GitHub.VisualStudio/Menus/ShowCurrentPullRequest.cs b/src/GitHub.VisualStudio/Menus/ShowCurrentPullRequest.cs index fb825ff0a7..8e4aa9fd24 100644 --- a/src/GitHub.VisualStudio/Menus/ShowCurrentPullRequest.cs +++ b/src/GitHub.VisualStudio/Menus/ShowCurrentPullRequest.cs @@ -26,6 +26,8 @@ public async void Activate(object data = null) try { var pullRequestSessionManager = ServiceProvider.ExportProvider.GetExportedValueOrDefault(); + await pullRequestSessionManager.EnsureInitialized(); + var session = pullRequestSessionManager?.CurrentSession; if (session == null) { From a39d0bb809f59f491df43faf14be5d1778684a18 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 8 Feb 2018 10:45:28 +0100 Subject: [PATCH 4/4] Fix typo. --- .../Services/IPullRequestSessionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitHub.Exports.Reactive/Services/IPullRequestSessionManager.cs b/src/GitHub.Exports.Reactive/Services/IPullRequestSessionManager.cs index 41c1a2f055..2d58852145 100644 --- a/src/GitHub.Exports.Reactive/Services/IPullRequestSessionManager.cs +++ b/src/GitHub.Exports.Reactive/Services/IPullRequestSessionManager.cs @@ -36,7 +36,7 @@ public interface IPullRequestSessionManager : INotifyPropertyChanged /// /// A task that when completed indicates that the service is initialized. /// - /// If you are simplying monitoring changes to the then you + /// If you are simply monitoring changes to the then you /// don't need to call this method: will be updated on /// initialization. If however, you want to be sure that is /// initialized, you can await the task returned from this method.