From a3e97dc4b90554db75e6e783dd5ee60bad483e7c Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 30 Aug 2016 17:07:23 +0200 Subject: [PATCH 01/68] Load the package asynchronously To load things async, nothing in the loading path can call GetService or GetGlobalService, since these will deadlock. This requires moving a bunch of initialization code out of constructors, so things can be initialized on request and after the package load codepath is done. --- src/GitHub.Exports/GitHub.Exports.csproj | 1 - src/GitHub.Exports/Services/IMenuProvider.cs | 2 + src/GitHub.Exports/Services/IUsageTracker.cs | 6 +- src/GitHub.Exports/Services/VSServices.cs | 7 +- src/GitHub.Exports/Settings/Guids.cs | 2 + .../GitHub.VisualStudio.csproj | 50 +++++++++++--- src/GitHub.VisualStudio/GitHubPackage.cs | 23 ++++--- .../Services/UIProvider.cs | 68 ++++++++++++------- .../Services/UsageTracker.cs | 60 ++++++++-------- src/GitHub.VisualStudio/packages.config | 9 +++ 10 files changed, 147 insertions(+), 81 deletions(-) rename src/{GitHub.Exports => GitHub.VisualStudio}/Services/UsageTracker.cs (88%) diff --git a/src/GitHub.Exports/GitHub.Exports.csproj b/src/GitHub.Exports/GitHub.Exports.csproj index 0a46b61699..8463698de5 100644 --- a/src/GitHub.Exports/GitHub.Exports.csproj +++ b/src/GitHub.Exports/GitHub.Exports.csproj @@ -145,7 +145,6 @@ - True diff --git a/src/GitHub.Exports/Services/IMenuProvider.cs b/src/GitHub.Exports/Services/IMenuProvider.cs index 5cb436425c..1492bfbfad 100644 --- a/src/GitHub.Exports/Services/IMenuProvider.cs +++ b/src/GitHub.Exports/Services/IMenuProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; namespace GitHub.VisualStudio { @@ -7,6 +8,7 @@ namespace GitHub.VisualStudio /// Container for static and dynamic visibility menus (context, toolbar, top, etc) /// Get a reference to this via MEF and register the menus /// + [Guid(Guids.MenuProviderId)] public interface IMenuProvider { /// diff --git a/src/GitHub.Exports/Services/IUsageTracker.cs b/src/GitHub.Exports/Services/IUsageTracker.cs index 1457356965..4b43cdb202 100644 --- a/src/GitHub.Exports/Services/IUsageTracker.cs +++ b/src/GitHub.Exports/Services/IUsageTracker.cs @@ -1,5 +1,9 @@ -namespace GitHub.Services +using GitHub.VisualStudio; +using System.Runtime.InteropServices; + +namespace GitHub.Services { + [Guid(Guids.UsageTrackerId)] public interface IUsageTracker { void IncrementLaunchCount(); diff --git a/src/GitHub.Exports/Services/VSServices.cs b/src/GitHub.Exports/Services/VSServices.cs index dc9b237e6a..c43b399305 100644 --- a/src/GitHub.Exports/Services/VSServices.cs +++ b/src/GitHub.Exports/Services/VSServices.cs @@ -72,8 +72,8 @@ public void ActivityLogWarning(string message) const string EnvVersionKey = "EnvVersion"; string GetVSVersion() { - var version = VisualStudio.Services.Dte.Version; - var keyPath = String.Format(CultureInfo.InvariantCulture, "{0}\\{1}_Config\\SplashInfo", RegistryRootKey, version); + var version = typeof(Microsoft.VisualStudio.Shell.ActivityLog).Assembly.GetName().Version; + var keyPath = String.Format(CultureInfo.InvariantCulture, "{0}\\{1}.{2}_Config\\SplashInfo", RegistryRootKey, version.Major, version.Minor); try { using (var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(keyPath)) @@ -82,6 +82,7 @@ string GetVSVersion() if (!String.IsNullOrEmpty(value)) return value; } + // fallback to poking the CommonIDE assembly, which most closely follows the advertised version. var asm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName.StartsWith("Microsoft.VisualStudio.CommonIDE", StringComparison.OrdinalIgnoreCase)); if (asm != null) return asm.GetName().Version.ToString(); @@ -90,7 +91,7 @@ string GetVSVersion() { VsOutputLogger.WriteLine(string.Format(CultureInfo.CurrentCulture, "Error getting the Visual Studio version '{0}'", ex)); } - return version; + return version.ToString(); } } } diff --git a/src/GitHub.Exports/Settings/Guids.cs b/src/GitHub.Exports/Settings/Guids.cs index db568407cd..1cbb4a43b5 100644 --- a/src/GitHub.Exports/Settings/Guids.cs +++ b/src/GitHub.Exports/Settings/Guids.cs @@ -6,5 +6,7 @@ public static class Guids { public const string PackageId = "c3d3dc68-c977-411f-b3e8-03b0dccf7dfc"; public const string ImagesId = "27841f47-070a-46d6-90be-a5cbbfc724ac"; + public const string MenuProviderId = "36FA083F-E0BE-418E-B42F-CB7623C55A00"; + public const string UsageTrackerId = "9362DD38-7E49-4B5D-9DE1-E843D4155716"; } } diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj index d08a2b0bdb..13900a221a 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj @@ -107,6 +107,11 @@ + + True + ..\..\packages\Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.14.3.25407\lib\Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.dll + True + ..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll True @@ -124,18 +129,34 @@ ..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll True + + True + ..\..\packages\Microsoft.VisualStudio.Shell.Interop.10.0.10.0.30319\lib\Microsoft.VisualStudio.Shell.Interop.10.0.dll + True + + + True + ..\..\packages\Microsoft.VisualStudio.Shell.Interop.11.0.11.0.61030\lib\Microsoft.VisualStudio.Shell.Interop.11.0.dll + True + + + True + ..\..\packages\Microsoft.VisualStudio.Shell.Interop.12.0.12.0.30110\lib\Microsoft.VisualStudio.Shell.Interop.12.0.dll + True + + + True + ..\..\packages\Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.14.3.25407\lib\Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll + True + + + ..\..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll + True + - - - - False - - - true - @@ -143,6 +164,18 @@ ..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll True + + ..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll + True + + + ..\..\packages\Microsoft.VisualStudio.Threading.14.1.131\lib\net45\Microsoft.VisualStudio.Threading.dll + True + + + ..\..\packages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll + True + ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll True @@ -237,6 +270,7 @@ + diff --git a/src/GitHub.VisualStudio/GitHubPackage.cs b/src/GitHub.VisualStudio/GitHubPackage.cs index 21435bd9f9..857fcda512 100644 --- a/src/GitHub.VisualStudio/GitHubPackage.cs +++ b/src/GitHub.VisualStudio/GitHubPackage.cs @@ -26,7 +26,7 @@ namespace GitHub.VisualStudio [ProvideAutoLoad("11B8E6D7-C08B-4385-B321-321078CDD1F8")] [ProvideToolWindow(typeof(GitHubPane), Orientation = ToolWindowOrientation.Right, Style = VsDockStyle.Tabbed, Window = EnvDTE.Constants.vsWindowKindSolutionExplorer)] [ProvideOptionPage(typeof(OptionsPage), "GitHub for Visual Studio", "General", 0, 0, supportsAutomation: true)] - public class GitHubPackage : Package + public class GitHubPackage : AsyncPackage { readonly IServiceProvider serviceProvider; @@ -46,24 +46,23 @@ public GitHubPackage(IServiceProvider serviceProvider) this.serviceProvider = serviceProvider; } - protected override void Initialize() + protected override async tasks.Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) { - base.Initialize(); - IncrementLaunchCount(); + var usageTracker = await GetServiceAsync(typeof(IUsageTracker)) as IUsageTracker; + await tasks.Task.Run(() => usageTracker.IncrementLaunchCount()); + var menus = await GetServiceAsync(typeof(IMenuProvider)) as IMenuProvider; + + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - var menus = serviceProvider.GetExportedValue(); foreach (var menu in menus.Menus) serviceProvider.AddCommandHandler(menu.Guid, menu.CmdId, (s, e) => menu.Activate()); foreach (var menu in menus.DynamicMenus) serviceProvider.AddCommandHandler(menu.Guid, menu.CmdId, menu.CanShow, () => menu.Activate()); - } - void IncrementLaunchCount() - { - var usageTracker = serviceProvider.GetExportedValue(); - usageTracker.IncrementLaunchCount(); + await base.InitializeAsync(cancellationToken, progress); } + } [Export(typeof(IGitHubClient))] @@ -79,6 +78,8 @@ public GHClient(IProgram program) [NullGuard.NullGuard(NullGuard.ValidationFlags.None)] [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] [ProvideService(typeof(IUIProvider), IsAsyncQueryable = true)] + [ProvideService(typeof(IMenuProvider), IsAsyncQueryable = true)] + [ProvideService(typeof(IUsageTracker), IsAsyncQueryable = true)] [ProvideAutoLoad(UIContextGuids.NoSolution)] [ProvideAutoLoad(UIContextGuids.SolutionExists)] [Guid(ServiceProviderPackageId)] @@ -115,6 +116,8 @@ Version VSVersion protected override async tasks.Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) { AddService(typeof(IUIProvider), CreateService, true); + AddService(typeof(IMenuProvider), CreateService, true); + AddService(typeof(IUsageTracker), CreateService, true); // Load the start page package only for Dev15 Preview 4 if (VSVersion.Major == 15 && VSVersion.Build == 25618) diff --git a/src/GitHub.VisualStudio/Services/UIProvider.cs b/src/GitHub.VisualStudio/Services/UIProvider.cs index a992b4549f..30991dae95 100644 --- a/src/GitHub.VisualStudio/Services/UIProvider.cs +++ b/src/GitHub.VisualStudio/Services/UIProvider.cs @@ -34,19 +34,56 @@ class OwnedComposablePart static readonly Logger log = LogManager.GetCurrentClassLogger(); CompositeDisposable disposables = new CompositeDisposable(); readonly IServiceProvider serviceProvider; - CompositionContainer tempContainer; readonly Dictionary tempParts; ExportLifetimeContext currentUIFlow; readonly Version currentVersion; bool initializingLogging = false; + ExportProvider exportProvider = null; [AllowNull] - public ExportProvider ExportProvider { get; } + public ExportProvider ExportProvider + { + get + { + if (exportProvider == null) + { + var componentModel = serviceProvider.GetService(typeof(SComponentModel)) as IComponentModel; + Debug.Assert(componentModel != null, "Service of type SComponentModel not found"); + if (componentModel == null) + { + log.Error("Service of type SComponentModel not found"); + } + exportProvider = componentModel.DefaultExportProvider; + + if (ExportProvider == null) + { + log.Error("DefaultExportProvider could not be obtained."); + } + } + return exportProvider; + } + } + + CompositionContainer tempContainer; + CompositionContainer TempContainer + { + get + { + if (tempContainer == null) + { + tempContainer = AddToDisposables(new CompositionContainer(new ComposablePartExportProvider() + { + SourceProvider = ExportProvider + })); + } + return tempContainer; + } + } [AllowNull] public IServiceProvider GitServiceProvider { get; set; } - bool Initialized { get { return ExportProvider != null; } } + bool Initialized { get { return exportProvider != null; } } [ImportingConstructor] public UIProvider([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) @@ -54,25 +91,6 @@ public UIProvider([Import(typeof(SVsServiceProvider))] IServiceProvider serviceP this.currentVersion = typeof(UIProvider).Assembly.GetName().Version; this.serviceProvider = serviceProvider; - var componentModel = serviceProvider.GetService(typeof(SComponentModel)) as IComponentModel; - Debug.Assert(componentModel != null, "Service of type SComponentModel not found"); - if (componentModel == null) - { - log.Error("Service of type SComponentModel not found"); - return; - } - ExportProvider = componentModel.DefaultExportProvider; - - if (ExportProvider == null) - { - log.Error("DefaultExportProvider could not be obtained."); - return; - } - - tempContainer = AddToDisposables(new CompositionContainer(new ComposablePartExportProvider() - { - SourceProvider = ExportProvider - })); tempParts = new Dictionary(); } @@ -96,7 +114,7 @@ public object TryGetService(Type serviceType) } string contract = AttributedModelServices.GetContractName(serviceType); - var instance = AddToDisposables(tempContainer.GetExportedValueOrDefault(contract)); + var instance = AddToDisposables(TempContainer.GetExportedValueOrDefault(contract)); if (instance != null) return instance; @@ -177,7 +195,7 @@ public void AddService(Type t, object owner, object instance) var part = batch.AddExportedValue(contract, instance); Debug.Assert(part != null, "Adding an exported value must return a non-null part"); tempParts.Add(contract, new OwnedComposablePart { Owner = owner, Part = part }); - tempContainer.Compose(batch); + TempContainer.Compose(batch); } /// @@ -205,7 +223,7 @@ public void RemoveService(Type t, [AllowNull] object owner) tempParts.Remove(contract); var batch = new CompositionBatch(); batch.RemovePart(part.Part); - tempContainer.Compose(batch); + TempContainer.Compose(batch); } } diff --git a/src/GitHub.Exports/Services/UsageTracker.cs b/src/GitHub.VisualStudio/Services/UsageTracker.cs similarity index 88% rename from src/GitHub.Exports/Services/UsageTracker.cs rename to src/GitHub.VisualStudio/Services/UsageTracker.cs index 1502c1b8e9..116a3b0c41 100644 --- a/src/GitHub.Exports/Services/UsageTracker.cs +++ b/src/GitHub.VisualStudio/Services/UsageTracker.cs @@ -21,12 +21,13 @@ public class UsageTracker : IUsageTracker const string StoreFileName = "ghfvs.usage"; static readonly Calendar cal = CultureInfo.InvariantCulture.Calendar; - readonly IMetricsService client; + IMetricsService client; readonly IConnectionManager connectionManager; - readonly IPackageSettings userSettings; + IPackageSettings userSettings; readonly IVSServices vsservices; readonly DispatcherTimer timer; readonly string storePath; + readonly IServiceProvider serviceProvider; Func fileExists; Func readAllText; @@ -37,10 +38,11 @@ public class UsageTracker : IUsageTracker public UsageTracker( IProgram program, IConnectionManager connectionManager, - IPackageSettings userSettings, IVSServices vsservices, [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) { + this.serviceProvider = serviceProvider; + fileExists = (path) => System.IO.File.Exists(path); readAllText = (path, encoding) => { @@ -64,9 +66,7 @@ public UsageTracker( dirCreate = (path) => System.IO.Directory.CreateDirectory(path); this.connectionManager = connectionManager; - this.userSettings = userSettings; this.vsservices = vsservices; - this.client = serviceProvider.GetExportedValue(); this.timer = new DispatcherTimer( TimeSpan.FromMinutes(1), DispatcherPriority.Background, @@ -77,15 +77,7 @@ public UsageTracker( program.ApplicationName, StoreFileName); - userSettings.PropertyChanged += (s, e) => - { - if (e.PropertyName == nameof(userSettings.CollectMetrics)) - { - UpdateTimer(false); - } - }; - - UpdateTimer(true); + RunTimer(); } public void IncrementLaunchCount() @@ -182,25 +174,12 @@ void SaveUsage(UsageStore store) writeAllText(storePath, json, Encoding.UTF8); } - void UpdateTimer(bool initialCall) + void RunTimer() { - // If the method was called due to userSettings.CollectMetrics changing, then send an - // opt-in/out message. - if (!initialCall && client != null) - { - if (userSettings.CollectMetrics) - client.SendOptIn(); - else - client.SendOptOut(); - } - - // The timer first ticks after 1 minute to allow things to settle down after startup. + // The timer first ticks after 3 minutes to allow things to settle down after startup. // This will be changed to 8 hours after the first tick by the TimerTick method. - timer.Stop(); - timer.Interval = TimeSpan.FromMinutes(1); - - if (userSettings.CollectMetrics && client != null) - timer.Start(); + timer.Interval = TimeSpan.FromMinutes(3); + timer.Start(); } async void TimerTick(object sender, EventArgs e) @@ -210,9 +189,24 @@ async void TimerTick(object sender, EventArgs e) // Subsequent timer ticks should occur every 8 hours. timer.Interval = TimeSpan.FromHours(8); + if (userSettings == null) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + client = serviceProvider.GetExportedValue(); + if (client == null) + { + timer.Stop(); + return; + } + userSettings = serviceProvider.GetExportedValue(); + } + + if (!userSettings.CollectMetrics) + return; + try { - // Every time we increment the launch count we increment both daily and weekly + // Every time we increment the launch count we increment both daily and weekly // launch count but we only submit (and clear) the weekly launch count when we've // transitioned into a new week. We've defined a week by the ISO8601 definition, // i.e. week starting on Monday and ending on Sunday. @@ -258,7 +252,7 @@ async Task SendUsage(UsageModel usage, bool weekly, bool monthly) // http://blogs.msdn.com/b/shawnste/archive/2006/01/24/iso-8601-week-of-year-format-in-microsoft-net.aspx static int GetIso8601WeekOfYear(DateTimeOffset time) { - // Seriously cheat. If its Monday, Tuesday or Wednesday, then it'll + // Seriously cheat. If its Monday, Tuesday or Wednesday, then it'll // be the same week# as whatever Thursday, Friday or Saturday are, // and we always get those right DayOfWeek day = cal.GetDayOfWeek(time.UtcDateTime); diff --git a/src/GitHub.VisualStudio/packages.config b/src/GitHub.VisualStudio/packages.config index 3540ac0180..9ce6138e99 100644 --- a/src/GitHub.VisualStudio/packages.config +++ b/src/GitHub.VisualStudio/packages.config @@ -4,11 +4,20 @@ + + + + + + + + + From 348c71b01d8b28c692be20cc38d9c2a8c7f4f32a Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 19 Oct 2016 14:15:23 +0200 Subject: [PATCH 02/68] Bump version to 2.0.15.2 --- src/GitHub.VisualStudio/GitHub.VisualStudio.csproj | 2 +- src/GitHub.VisualStudio/source.extension.vsixmanifest | 2 +- src/MsiInstaller/Version.wxi | 2 +- src/common/SolutionInfo.cs | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj index 13900a221a..ebcb504898 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj @@ -9,7 +9,7 @@ Internal - 2.0.15.1 + 2.0.15.2 ..\..\build\$(Configuration)\ diff --git a/src/GitHub.VisualStudio/source.extension.vsixmanifest b/src/GitHub.VisualStudio/source.extension.vsixmanifest index 42828e4aa3..6257ace21d 100644 --- a/src/GitHub.VisualStudio/source.extension.vsixmanifest +++ b/src/GitHub.VisualStudio/source.extension.vsixmanifest @@ -1,7 +1,7 @@  - + GitHub Extension for Visual Studio A Visual Studio Extension that brings the GitHub Flow into Visual Studio. https://visualstudio.github.com diff --git a/src/MsiInstaller/Version.wxi b/src/MsiInstaller/Version.wxi index ec833b6d7a..f117328304 100644 --- a/src/MsiInstaller/Version.wxi +++ b/src/MsiInstaller/Version.wxi @@ -1,4 +1,4 @@ - + diff --git a/src/common/SolutionInfo.cs b/src/common/SolutionInfo.cs index fbc3caec5c..4c522c6750 100644 --- a/src/common/SolutionInfo.cs +++ b/src/common/SolutionInfo.cs @@ -3,8 +3,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyProduct("GitHub Extension for Visual Studio")] -[assembly: AssemblyVersion("2.0.15.1")] -[assembly: AssemblyFileVersion("2.0.15.1")] +[assembly: AssemblyVersion("2.0.15.2")] +[assembly: AssemblyFileVersion("2.0.15.2")] [assembly: ComVisible(false)] [assembly: AssemblyCompany("GitHub, Inc.")] [assembly: AssemblyCopyright("Copyright © GitHub, Inc. 2014-2016")] @@ -16,6 +16,6 @@ namespace System { internal static class AssemblyVersionInformation { - internal const string Version = "2.0.15.1"; + internal const string Version = "2.0.15.2"; } } From 6be70bc9fccd52c39dba8d5fc3e1e95630733d11 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 19 Oct 2016 14:16:52 +0200 Subject: [PATCH 03/68] Only run t4 generation in VS 2015 --- src/GitHub.Exports/GitHub.Exports.csproj | 1 - src/GitHub.VisualStudio/GitHub.VisualStudio.csproj | 1 - src/common/t4.targets | 3 ++- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/GitHub.Exports/GitHub.Exports.csproj b/src/GitHub.Exports/GitHub.Exports.csproj index 8463698de5..a8e737c347 100644 --- a/src/GitHub.Exports/GitHub.Exports.csproj +++ b/src/GitHub.Exports/GitHub.Exports.csproj @@ -208,7 +208,6 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj index ebcb504898..6062f8bc4d 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj @@ -664,7 +664,6 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. diff --git a/src/common/t4.targets b/src/common/t4.targets index a956c74040..e4ef2c586a 100644 --- a/src/common/t4.targets +++ b/src/common/t4.targets @@ -1,7 +1,8 @@ + - true + true true $(SolutionDir)\packages From 180f5bdd1533e7802d2462a81a0d35d08e01de82 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 19 Oct 2016 14:19:45 +0200 Subject: [PATCH 04/68] Get rid of EventBuilder --- GitHubVS.sln | 18 ------------------ submodules/reactiveui | 2 +- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/GitHubVS.sln b/GitHubVS.sln index 26f9a22363..46b5b67330 100644 --- a/GitHubVS.sln +++ b/GitHubVS.sln @@ -81,8 +81,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveUI_Net45", "submodu EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveUI.Events_Net45", "submodules\reactiveui\ReactiveUI.Events\ReactiveUI.Events_Net45.csproj", "{600998C4-54DD-4755-BFA8-6F44544D8E2E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventBuilder", "submodules\reactiveui\ReactiveUI.Events\EventBuilder.csproj", "{3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Akavache", "Akavache", "{1E7F7253-A6AF-43C4-A955-37BEDDA01AC9}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akavache_Net45", "submodules\akavache\Akavache\Akavache_Net45.csproj", "{B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}" @@ -374,21 +372,6 @@ Global {600998C4-54DD-4755-BFA8-6F44544D8E2E}.XamlDesign|Any CPU.Build.0 = Release|Any CPU {600998C4-54DD-4755-BFA8-6F44544D8E2E}.XamlDesign|x86.ActiveCfg = Release|Any CPU {600998C4-54DD-4755-BFA8-6F44544D8E2E}.XamlDesign|x86.Build.0 = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Debug|Any CPU.ActiveCfg = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Debug|Any CPU.Build.0 = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Debug|x86.ActiveCfg = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Debug|x86.Build.0 = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Publish|x86.ActiveCfg = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Publish|x86.Build.0 = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Release|Any CPU.Build.0 = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Release|x86.ActiveCfg = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Release|x86.Build.0 = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.XamlDesign|Any CPU.ActiveCfg = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.XamlDesign|Any CPU.Build.0 = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.XamlDesign|x86.ActiveCfg = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.XamlDesign|x86.Build.0 = Release|Any CPU {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}.Debug|Any CPU.ActiveCfg = Release|Any CPU {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}.Debug|Any CPU.Build.0 = Release|Any CPU {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}.Debug|x86.ActiveCfg = Release|Any CPU @@ -574,7 +557,6 @@ Global {1E7F7253-A6AF-43C4-A955-37BEDDA01AB9} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB8} {1CE2D235-8072-4649-BA5A-CFB1AF8776E0} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB9} {600998C4-54DD-4755-BFA8-6F44544D8E2E} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB9} - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB9} {1E7F7253-A6AF-43C4-A955-37BEDDA01AC9} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB8} {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AC9} {241C47DF-CA8E-4296-AA03-2C48BB646ABD} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AC9} diff --git a/submodules/reactiveui b/submodules/reactiveui index 802d85c4d1..debdcac3bd 160000 --- a/submodules/reactiveui +++ b/submodules/reactiveui @@ -1 +1 @@ -Subproject commit 802d85c4d1a41185fe7d21a17a225e87be4a4654 +Subproject commit debdcac3bde5b754d0a53c3c4d6b27b0fc75412f From 85ac539bdbb2949f6d17689e6324304c7d45f434 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 4 Oct 2016 15:20:48 +0100 Subject: [PATCH 05/68] Fix submodule url to be public (fixes #571) --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 992b0dbe73..c8fcb1dca5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -18,4 +18,4 @@ url = git@github.com:github/VisualStudioBuildScripts [submodule "submodules/externalpackages/StartPage"] path = submodules/externalpackages/StartPage - url = git@github.com:editor-tools/StartPage.git + url = https://github.com/editor-tools/StartPage.git From 645b2a1279f7d9cdf301d45a64d9f1a226c64d69 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 19 Oct 2016 14:30:58 +0200 Subject: [PATCH 06/68] Switch to dedicated fork for rothko and get fody version bump --- .gitmodules | 2 +- submodules/rothko | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index c8fcb1dca5..4609f2bfa2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "submodules/rothko"] path = submodules/rothko - url = https://github.com/Haacked/Rothko.git + url = https://github.com/editor-tools/Rothko.git [submodule "submodules/reactiveui"] path = submodules/reactiveui url = https://github.com/shana/ReactiveUI diff --git a/submodules/rothko b/submodules/rothko index 581164318b..f5fe295b12 160000 --- a/submodules/rothko +++ b/submodules/rothko @@ -1 +1 @@ -Subproject commit 581164318b73b52617a20f0184135246872dcc9e +Subproject commit f5fe295b120626dae67570095df7f500a65c4850 From b8b8dad28edef4ac3d10da255ab6b174f97fcf74 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 19 Oct 2016 14:35:24 +0200 Subject: [PATCH 07/68] Bump fody version --- .../CredentialManagement.csproj | 10 +++++----- src/CredentialManagement/packages.config | 4 ++-- src/GitHub.App/GitHub.App.csproj | 10 +++++----- src/GitHub.App/packages.config | 4 ++-- .../GitHub.Extensions.Reactive.csproj | 14 +++++++------- src/GitHub.Extensions.Reactive/packages.config | 4 ++-- src/GitHub.Extensions/GitHub.Extensions.csproj | 14 +++++++------- src/GitHub.Extensions/packages.config | 4 ++-- .../GitHub.TeamFoundation.14.csproj | 10 ++++++---- src/GitHub.TeamFoundation.14/packages.config | 4 ++-- .../GitHub.TeamFoundation.15.csproj | 10 ++++++---- src/GitHub.TeamFoundation.15/packages.config | 4 ++-- src/GitHub.UI.Reactive/GitHub.UI.Reactive.csproj | 14 +++++++------- src/GitHub.UI.Reactive/packages.config | 4 ++-- src/GitHub.UI/GitHub.UI.csproj | 13 +++++++------ src/GitHub.UI/packages.config | 4 ++-- .../GitHub.VisualStudio.UI.csproj | 10 ++++++++-- src/GitHub.VisualStudio.UI/packages.config | 4 ++-- src/GitHub.VisualStudio/GitHub.VisualStudio.csproj | 10 +++++----- src/GitHub.VisualStudio/packages.config | 4 ++-- src/UnitTests/UnitTests.csproj | 4 ++-- src/UnitTests/packages.config | 2 +- 22 files changed, 86 insertions(+), 75 deletions(-) diff --git a/src/CredentialManagement/CredentialManagement.csproj b/src/CredentialManagement/CredentialManagement.csproj index d801e2176e..d54af939cb 100644 --- a/src/CredentialManagement/CredentialManagement.csproj +++ b/src/CredentialManagement/CredentialManagement.csproj @@ -38,9 +38,9 @@ false - - ..\..\packages\NullGuard.Fody.1.4.1\Lib\portable-net4+sl4+wp7+win8+MonoAndroid16+MonoTouch40\NullGuard.dll - True + + ..\..\packages\NullGuard.Fody.1.4.6\Lib\dotnet\NullGuard.dll + False @@ -79,12 +79,12 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + @@ -262,6 +266,8 @@ + + diff --git a/src/GitHub.VisualStudio/GitHubPackage.cs b/src/GitHub.VisualStudio/GitHubPackage.cs index 857fcda512..30159f3496 100644 --- a/src/GitHub.VisualStudio/GitHubPackage.cs +++ b/src/GitHub.VisualStudio/GitHubPackage.cs @@ -10,15 +10,17 @@ using Microsoft.VisualStudio.Shell.Interop; using Octokit; using GitHub.Helpers; -using System.ComponentModel.Design; using System.Diagnostics; using System.Threading; -using tasks = System.Threading.Tasks; +using System.Threading.Tasks; +using Task = System.Threading.Tasks.Task; +using Microsoft.VisualStudio.Threading; using Microsoft.VisualStudio.ComponentModelHost; +using GitHub.VisualStudio.Menus; namespace GitHub.VisualStudio { - [PackageRegistration(UseManagedResourcesOnly = true)] + [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] [Guid(GuidList.guidGitHubPkgString)] [ProvideMenuResource("Menus.ctmenu", 1)] @@ -29,6 +31,7 @@ namespace GitHub.VisualStudio public class GitHubPackage : AsyncPackage { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] readonly IServiceProvider serviceProvider; static GitHubPackage() @@ -46,10 +49,12 @@ public GitHubPackage(IServiceProvider serviceProvider) this.serviceProvider = serviceProvider; } - protected override async tasks.Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) + protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) { - var usageTracker = await GetServiceAsync(typeof(IUsageTracker)) as IUsageTracker; - await tasks.Task.Run(() => usageTracker.IncrementLaunchCount()); + await base.InitializeAsync(cancellationToken, progress); + + //var usageTracker = await GetServiceAsync(typeof(IUsageTracker)) as IUsageTracker; + //usageTracker.IncrementLaunchCount(); var menus = await GetServiceAsync(typeof(IMenuProvider)) as IMenuProvider; await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); @@ -59,8 +64,6 @@ protected override async tasks.Task InitializeAsync(CancellationToken cancellati foreach (var menu in menus.DynamicMenus) serviceProvider.AddCommandHandler(menu.Guid, menu.CmdId, menu.CanShow, () => menu.Activate()); - - await base.InitializeAsync(cancellationToken, progress); } } @@ -75,10 +78,32 @@ public GHClient(IProgram program) } } + //[NullGuard.NullGuard(NullGuard.ValidationFlags.None)] + //[PackageRegistration(UseManagedResourcesOnly = true)] + //[ProvideAutoLoad(MenuLoadingContextId)] + //[ProvideUIContextRule(MenuLoadingContextId, + // name: "GitHub context menus", + // expression: "FileOpen", + // termNames: new[] { "FileOpen" }, + // termValues: new[] { "ActiveEditorContentType:CSharp" } + //)] + //[Guid(MenuRegistrationPackageId)] + //public sealed class MenuRegistrationPackage : Package + //{ + // const string MenuRegistrationPackageId = "E37D3B17-2255-4144-9802-349530796693"; + // const string MenuLoadingContextId = "F2CC8C27-AF24-4BA6-80BC-4819A0E8844F"; + + // protected override void Initialize() + // { + // base.Initialize(); + + + // } + //} + [NullGuard.NullGuard(NullGuard.ValidationFlags.None)] [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] [ProvideService(typeof(IUIProvider), IsAsyncQueryable = true)] - [ProvideService(typeof(IMenuProvider), IsAsyncQueryable = true)] [ProvideService(typeof(IUsageTracker), IsAsyncQueryable = true)] [ProvideAutoLoad(UIContextGuids.NoSolution)] [ProvideAutoLoad(UIContextGuids.SolutionExists)] @@ -87,6 +112,7 @@ public sealed class ServiceProviderPackage : AsyncPackage { const string ServiceProviderPackageId = "D5CE1488-DEDE-426D-9E5B-BFCCFBE33E53"; const string StartPagePreview4PackageId = "3b764d23-faf7-486f-94c7-b3accc44a70d"; + const string StartPagePreview5PackageId = "3b764d23-faf7-486f-94c7-b3accc44a70e"; Version vsversion; Version VSVersion @@ -113,33 +139,57 @@ Version VSVersion } } - protected override async tasks.Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) + protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) { AddService(typeof(IUIProvider), CreateService, true); - AddService(typeof(IMenuProvider), CreateService, true); AddService(typeof(IUsageTracker), CreateService, true); + AddService(typeof(IMenuProvider), CreateService, true); - // Load the start page package only for Dev15 Preview 4 - if (VSVersion.Major == 15 && VSVersion.Build == 25618) + if (VSVersion.Major == 15) { - var shell = await GetServiceAsync(typeof(SVsShell)) as IVsShell; - IVsPackage startPagePackage; - if (ErrorHandler.Failed(shell?.LoadPackage(new Guid(StartPagePreview4PackageId), out startPagePackage) ?? -1)) + // Load the start page package only for Dev15 Preview 4 + if (VSVersion.Build == 25618) + { + var shell = await GetServiceAsync(typeof(SVsShell)) as IVsShell; + IVsPackage startPagePackage; + if (ErrorHandler.Failed(shell?.LoadPackage(new Guid(StartPagePreview4PackageId), out startPagePackage) ?? -1)) + { + // ¯\_(ツ)_/¯ + } + } + // Load the start page package only for Dev15 Preview 5 + else if (VSVersion.Build == 25807) { - // ¯\_(ツ)_/¯ + var shell = await GetServiceAsync(typeof(SVsShell)) as IVsShell; + IVsPackage startPagePackage; + if (ErrorHandler.Failed(shell?.LoadPackage(new Guid(StartPagePreview5PackageId), out startPagePackage) ?? -1)) + { + // ¯\_(ツ)_/¯ + } } } } - async tasks.Task CreateService(IAsyncServiceContainer container, CancellationToken cancellationToken, Type serviceType) + async Task CreateService(IAsyncServiceContainer container, CancellationToken cancellationToken, Type serviceType) { if (serviceType == null) return null; - string contract = AttributedModelServices.GetContractName(serviceType); - var cm = await GetServiceAsync(typeof(SComponentModel)) as IComponentModel; - if (cm == null) - return null; - return await tasks.Task.Run(() => cm.DefaultExportProvider.GetExportedValueOrDefault(contract)); + + if (serviceType == typeof(IUIProvider)) + { + return new UIProvider(this); + } + else if (serviceType == typeof(IMenuProvider)) + { + var sp = await GetServiceAsync(typeof(IUIProvider)) as IUIProvider; + return new MenuProvider(sp); + } + // go the mef route + else + { + var sp = await GetServiceAsync(typeof(IUIProvider)) as IUIProvider; + return sp.TryGetService(serviceType); + } } } } diff --git a/src/GitHub.VisualStudio/Menus/AddConnection.cs b/src/GitHub.VisualStudio/Menus/AddConnection.cs index 2c78c91dce..ffc12353d1 100644 --- a/src/GitHub.VisualStudio/Menus/AddConnection.cs +++ b/src/GitHub.VisualStudio/Menus/AddConnection.cs @@ -1,21 +1,14 @@ -using Microsoft.VisualStudio.Shell; -using System; -using System.ComponentModel.Composition; -using GitHub.Services; +using GitHub.Services; using GitHub.UI; -using GitHub.Extensions; using NullGuard; -using GitHub.Api; +using System; namespace GitHub.VisualStudio.Menus { - [Export(typeof(IMenuHandler))] - [PartCreationPolicy(CreationPolicy.Shared)] public class AddConnection: MenuBase, IMenuHandler { - [ImportingConstructor] - public AddConnection([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider, ISimpleApiClientFactory apiFactory) - : base(serviceProvider, apiFactory) + public AddConnection(IUIProvider serviceProvider) + : base(serviceProvider) { } diff --git a/src/GitHub.VisualStudio/Menus/CopyLink.cs b/src/GitHub.VisualStudio/Menus/CopyLink.cs index bc80776563..889906000a 100644 --- a/src/GitHub.VisualStudio/Menus/CopyLink.cs +++ b/src/GitHub.VisualStudio/Menus/CopyLink.cs @@ -1,27 +1,17 @@ -using Microsoft.VisualStudio.Shell; -using System; -using System.ComponentModel.Composition; -using System.Windows; -using GitHub.Extensions; -using GitHub.Services; +using GitHub.Services; using GitHub.VisualStudio.UI; using NullGuard; -using GitHub.Api; +using System; +using System.Windows; namespace GitHub.VisualStudio.Menus { - [Export(typeof(IDynamicMenuHandler))] - [PartCreationPolicy(CreationPolicy.Shared)] public class CopyLink : LinkMenuBase, IDynamicMenuHandler { - readonly IUsageTracker usageTracker; - [ImportingConstructor] - public CopyLink([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider, - IUsageTracker usageTracker, ISimpleApiClientFactory apiFactory) - : base(serviceProvider, apiFactory) + public CopyLink(IUIProvider serviceProvider) + : base(serviceProvider) { - this.usageTracker = usageTracker; } public Guid Guid => GuidList.guidContextMenuSet; @@ -39,13 +29,13 @@ public async void Activate([AllowNull]object data = null) try { Clipboard.SetText(link); - var ns = ServiceProvider.GetExportedValue(); + var ns = ServiceProvider.TryGetService(); ns?.ShowMessage(Resources.LinkCopiedToClipboardMessage); - this.usageTracker.IncrementLinkToGitHubCount(); + UsageTracker.IncrementLinkToGitHubCount(); } catch { - var ns = ServiceProvider.GetExportedValue(); + var ns = ServiceProvider.TryGetService(); ns?.ShowMessage(Resources.Error_FailedToCopyToClipboard); } } diff --git a/src/GitHub.VisualStudio/Menus/CreateGist.cs b/src/GitHub.VisualStudio/Menus/CreateGist.cs index fb95413ad9..1a50215344 100644 --- a/src/GitHub.VisualStudio/Menus/CreateGist.cs +++ b/src/GitHub.VisualStudio/Menus/CreateGist.cs @@ -1,26 +1,20 @@ -using Microsoft.VisualStudio.Shell; -using System; -using System.ComponentModel.Composition; -using GitHub.Services; +using GitHub.Services; using GitHub.UI; -using GitHub.Extensions; -using System.Diagnostics; using NullGuard; +using System; +using System.Diagnostics; namespace GitHub.VisualStudio.Menus { - [Export(typeof(IDynamicMenuHandler))] - [PartCreationPolicy(CreationPolicy.Shared)] public class CreateGist : MenuBase, IDynamicMenuHandler { readonly Lazy selectedTextProvider; + ISelectedTextProvider SelectedTextProvider => selectedTextProvider.Value; - [ImportingConstructor] - public CreateGist([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider, - Lazy selectedTextProvider) + public CreateGist(IUIProvider serviceProvider) : base(serviceProvider) { - this.selectedTextProvider = selectedTextProvider; + selectedTextProvider = new Lazy(() => ServiceProvider.TryGetService()); } public Guid Guid { get { return GuidList.guidContextMenuSet; } } @@ -28,9 +22,8 @@ public CreateGist([Import(typeof(SVsServiceProvider))] IServiceProvider serviceP public bool CanShow() { - var stp = selectedTextProvider.Value; - Debug.Assert(stp != null, "Could not get an instance of ISelectedTextProvider"); - return !String.IsNullOrWhiteSpace(stp?.GetSelectedText()); + Debug.Assert(SelectedTextProvider != null, "Could not get an instance of ISelectedTextProvider"); + return !String.IsNullOrWhiteSpace(SelectedTextProvider?.GetSelectedText()); } public void Activate([AllowNull] object data) diff --git a/src/GitHub.VisualStudio/Menus/LinkMenuBase.cs b/src/GitHub.VisualStudio/Menus/LinkMenuBase.cs index 2e5d38c283..6540d6bf34 100644 --- a/src/GitHub.VisualStudio/Menus/LinkMenuBase.cs +++ b/src/GitHub.VisualStudio/Menus/LinkMenuBase.cs @@ -1,21 +1,25 @@ -using GitHub.Api; -using GitHub.Extensions; -using GitHub.Primitives; +using GitHub.Primitives; +using GitHub.Services; using System; namespace GitHub.VisualStudio.Menus { public class LinkMenuBase: MenuBase { - public LinkMenuBase(IServiceProvider serviceProvider, ISimpleApiClientFactory apiFactory) - : base(serviceProvider, apiFactory) + readonly Lazy usageTracker; + + protected IUsageTracker UsageTracker => usageTracker.Value; + + public LinkMenuBase(IUIProvider serviceProvider) + : base(serviceProvider) { + usageTracker = new Lazy(() => ServiceProvider.TryGetService()); } protected UriString GenerateLink() { var repo = ActiveRepo; - var activeDocument = ServiceProvider.GetExportedValue(); + var activeDocument = ServiceProvider.TryGetService(); if (activeDocument == null) return null; return repo.GenerateUrl(activeDocument.Name, activeDocument.StartLine, activeDocument.EndLine); diff --git a/src/GitHub.VisualStudio/Base/MenuBase.cs b/src/GitHub.VisualStudio/Menus/MenuBase.cs similarity index 66% rename from src/GitHub.VisualStudio/Base/MenuBase.cs rename to src/GitHub.VisualStudio/Menus/MenuBase.cs index 5c06640f55..b8f118b1ff 100644 --- a/src/GitHub.VisualStudio/Base/MenuBase.cs +++ b/src/GitHub.VisualStudio/Menus/MenuBase.cs @@ -1,24 +1,22 @@ -using System; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using GitHub.Extensions; +using GitHub.Api; using GitHub.Models; using GitHub.Primitives; using GitHub.Services; -using System.Threading.Tasks; -using GitHub.Api; -using NullGuard; using GitHub.UI; +using NullGuard; +using System; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; namespace GitHub.VisualStudio { public abstract class MenuBase { - readonly IServiceProvider serviceProvider; - readonly ISimpleApiClientFactory apiFactory; + readonly IUIProvider serviceProvider; + readonly Lazy apiFactory; - protected IServiceProvider ServiceProvider { get { return serviceProvider; } } + protected IUIProvider ServiceProvider { get { return serviceProvider; } } protected ISimpleRepositoryModel ActiveRepo { get; private set; } @@ -32,35 +30,29 @@ protected ISimpleApiClient SimpleApiClient set { if (simpleApiClient != value && value == null) - apiFactory.ClearFromCache(simpleApiClient); + ApiFactory.ClearFromCache(simpleApiClient); simpleApiClient = value; } } - protected ISimpleApiClientFactory ApiFactory => apiFactory; + protected ISimpleApiClientFactory ApiFactory => apiFactory.Value; protected MenuBase() - { - } + {} - protected MenuBase(IServiceProvider serviceProvider, ISimpleApiClientFactory apiFactory) - { - this.serviceProvider = serviceProvider; - this.apiFactory = apiFactory; - } - - protected MenuBase(IServiceProvider serviceProvider) + protected MenuBase(IUIProvider serviceProvider) { this.serviceProvider = serviceProvider; + apiFactory = new Lazy(() => ServiceProvider.TryGetService()); } protected ISimpleRepositoryModel GetActiveRepo() { - var activeRepo = ServiceProvider.GetExportedValue()?.ActiveRepo; + var activeRepo = ServiceProvider.TryGetService()?.ActiveRepo; // activeRepo can be null at this point because it is set elsewhere as the result of async operation that may not have completed yet. if (activeRepo == null) { - var path = ServiceProvider.GetExportedValue()?.GetActiveRepoPath() ?? String.Empty; + var path = ServiceProvider.TryGetService()?.GetActiveRepoPath() ?? String.Empty; try { activeRepo = !string.IsNullOrEmpty(path) ? new SimpleRepositoryModel(path) : null; @@ -75,28 +67,23 @@ protected ISimpleRepositoryModel GetActiveRepo() protected void StartFlow(UIControllerFlow controllerFlow) { - var uiProvider = ServiceProvider.GetExportedValue(); - Debug.Assert(uiProvider != null, "MenuBase:StartFlow:No UIProvider available."); - if (uiProvider == null) - return; - IConnection connection = null; if (controllerFlow != UIControllerFlow.Authentication) { var activeRepo = GetActiveRepo(); - connection = ServiceProvider.GetExportedValue()?.Connections + connection = ServiceProvider.TryGetService()?.Connections .FirstOrDefault(c => activeRepo?.CloneUrl?.RepositoryName != null && c.HostAddress.Equals(HostAddress.Create(activeRepo.CloneUrl))); } - uiProvider.RunUI(controllerFlow, connection); + ServiceProvider.RunUI(controllerFlow, connection); } void RefreshRepo() { - ActiveRepo = ServiceProvider.GetExportedValue().ActiveRepo; + ActiveRepo = ServiceProvider.TryGetService().ActiveRepo; if (ActiveRepo == null) { - var vsGitServices = ServiceProvider.GetExportedValue(); + var vsGitServices = ServiceProvider.TryGetService(); string path = vsGitServices?.GetActiveRepoPath() ?? String.Empty; try { @@ -117,8 +104,7 @@ protected async Task IsGitHubRepo() if (uri == null) return false; - Debug.Assert(apiFactory != null, "apiFactory cannot be null. Did you call the right constructor?"); - SimpleApiClient = apiFactory.Create(uri); + SimpleApiClient = ApiFactory.Create(uri); var isdotcom = HostAddress.IsGitHubDotComUri(uri.ToRepositoryUrl()); if (!isdotcom) diff --git a/src/GitHub.VisualStudio/Menus/MenuProvider.cs b/src/GitHub.VisualStudio/Menus/MenuProvider.cs index 082130dde6..1e0d49ca2e 100644 --- a/src/GitHub.VisualStudio/Menus/MenuProvider.cs +++ b/src/GitHub.VisualStudio/Menus/MenuProvider.cs @@ -1,23 +1,32 @@ -using System.Collections.Generic; +using GitHub.Services; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel.Composition; using System.Linq; namespace GitHub.VisualStudio.Menus { - [Export(typeof(IMenuProvider))] - [PartCreationPolicy(CreationPolicy.Shared)] public class MenuProvider : IMenuProvider { public IReadOnlyCollection Menus { get; } public IReadOnlyCollection DynamicMenus { get; } - [ImportingConstructor] - public MenuProvider([ImportMany] IEnumerable menus, [ImportMany] IEnumerable dynamicMenus) + public MenuProvider(IUIProvider serviceProvider) { - Menus = new ReadOnlyCollection(menus.ToList()); - DynamicMenus = new ReadOnlyCollection(dynamicMenus.ToList()); + Menus = new List + { + new AddConnection(serviceProvider), + new OpenPullRequests(), + new ShowGitHubPane() + }; + + DynamicMenus = new List + { + new CopyLink(serviceProvider), + new CreateGist(serviceProvider), + new OpenLink(serviceProvider) + }; } } } diff --git a/src/GitHub.VisualStudio/Menus/OpenLink.cs b/src/GitHub.VisualStudio/Menus/OpenLink.cs index 7488f00e79..dac26b948d 100644 --- a/src/GitHub.VisualStudio/Menus/OpenLink.cs +++ b/src/GitHub.VisualStudio/Menus/OpenLink.cs @@ -1,25 +1,14 @@ -using GitHub.Extensions; -using GitHub.Services; -using Microsoft.VisualStudio.Shell; -using System; -using System.ComponentModel.Composition; +using GitHub.Services; using NullGuard; -using GitHub.Api; +using System; namespace GitHub.VisualStudio.Menus { - [Export(typeof(IDynamicMenuHandler))] - [PartCreationPolicy(CreationPolicy.Shared)] public class OpenLink: LinkMenuBase, IDynamicMenuHandler { - readonly IUsageTracker usageTracker; - - [ImportingConstructor] - public OpenLink([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider, - IUsageTracker usageTracker, ISimpleApiClientFactory apiFactory) - : base(serviceProvider, apiFactory) + public OpenLink(IUIProvider serviceProvider) + : base(serviceProvider) { - this.usageTracker = usageTracker; } public Guid Guid => GuidList.guidContextMenuSet; @@ -34,10 +23,10 @@ public async void Activate([AllowNull]object data = null) var link = GenerateLink(); if (link == null) return; - var browser = ServiceProvider.GetExportedValue(); + var browser = ServiceProvider.TryGetService(); browser?.OpenUrl(link.ToUri()); - usageTracker.IncrementOpenInGitHubCount(); + UsageTracker.IncrementOpenInGitHubCount(); } public bool CanShow() diff --git a/src/GitHub.VisualStudio/Menus/OpenPullRequests.cs b/src/GitHub.VisualStudio/Menus/OpenPullRequests.cs index 64192ee7dd..557e560d38 100644 --- a/src/GitHub.VisualStudio/Menus/OpenPullRequests.cs +++ b/src/GitHub.VisualStudio/Menus/OpenPullRequests.cs @@ -3,13 +3,10 @@ using GitHub.VisualStudio.UI; using NullGuard; using System; -using System.ComponentModel.Composition; namespace GitHub.VisualStudio.Menus { - [ExportMenu(MenuType = MenuType.OpenPullRequests)] - [PartCreationPolicy(CreationPolicy.Shared)] - public class OpenPullRequests: MenuBase, IMenuHandler + public class OpenPullRequests : MenuBase, IMenuHandler { public Guid Guid => GuidList.guidGitHubCmdSet; public int CmdId => PkgCmdIDList.openPullRequestsCommand; diff --git a/src/GitHub.VisualStudio/Menus/ShowGitHubPane.cs b/src/GitHub.VisualStudio/Menus/ShowGitHubPane.cs index eb9b70aab6..f7fa9269f6 100644 --- a/src/GitHub.VisualStudio/Menus/ShowGitHubPane.cs +++ b/src/GitHub.VisualStudio/Menus/ShowGitHubPane.cs @@ -1,14 +1,9 @@ -using GitHub.Exports; -using GitHub.UI; -using GitHub.VisualStudio.UI; +using GitHub.VisualStudio.UI; using NullGuard; using System; -using System.ComponentModel.Composition; namespace GitHub.VisualStudio.Menus { - [ExportMenu(MenuType = MenuType.GitHubPane)] - [PartCreationPolicy(CreationPolicy.Shared)] public class ShowGitHubPane: MenuBase, IMenuHandler { public Guid Guid => GuidList.guidGitHubCmdSet; diff --git a/src/GitHub.VisualStudio/Services/UIProvider.cs b/src/GitHub.VisualStudio/Services/UIProvider.cs index 30991dae95..1d9e4842c2 100644 --- a/src/GitHub.VisualStudio/Services/UIProvider.cs +++ b/src/GitHub.VisualStudio/Services/UIProvider.cs @@ -9,7 +9,6 @@ using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; -using System.Windows.Controls; using GitHub.Infrastructure; using GitHub.Models; using GitHub.Services; @@ -18,6 +17,10 @@ using Microsoft.VisualStudio.Shell; using NLog; using NullGuard; +using System.Threading; +using System.Threading.Tasks; +using Task = System.Threading.Tasks.Task; +using Microsoft.VisualStudio.Threading; namespace GitHub.VisualStudio { @@ -39,30 +42,7 @@ class OwnedComposablePart readonly Version currentVersion; bool initializingLogging = false; - ExportProvider exportProvider = null; - [AllowNull] - public ExportProvider ExportProvider - { - get - { - if (exportProvider == null) - { - var componentModel = serviceProvider.GetService(typeof(SComponentModel)) as IComponentModel; - Debug.Assert(componentModel != null, "Service of type SComponentModel not found"); - if (componentModel == null) - { - log.Error("Service of type SComponentModel not found"); - } - exportProvider = componentModel.DefaultExportProvider; - - if (ExportProvider == null) - { - log.Error("DefaultExportProvider could not be obtained."); - } - } - return exportProvider; - } - } + public ExportProvider ExportProvider { get; } CompositionContainer tempContainer; CompositionContainer TempContainer @@ -83,7 +63,7 @@ CompositionContainer TempContainer [AllowNull] public IServiceProvider GitServiceProvider { get; set; } - bool Initialized { get { return exportProvider != null; } } + public bool Initialized { get { return ExportProvider != null; } } [ImportingConstructor] public UIProvider([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) @@ -92,13 +72,39 @@ public UIProvider([Import(typeof(SVsServiceProvider))] IServiceProvider serviceP this.serviceProvider = serviceProvider; tempParts = new Dictionary(); + + var asyncProvider = serviceProvider as IAsyncServiceProvider; + IComponentModel componentModel = null; + if (asyncProvider != null) + { + componentModel = asyncProvider.GetServiceAsync(typeof(SComponentModel)) as IComponentModel; + } + else + { + componentModel = serviceProvider.GetService(typeof(SComponentModel)) as IComponentModel; + } + + Debug.Assert(componentModel != null, "Service of type SComponentModel not found"); + if (componentModel == null) + { + log.Error("Service of type SComponentModel not found"); + } + + ExportProvider = componentModel.DefaultExportProvider; + if (ExportProvider == null) + { + log.Error("DefaultExportProvider could not be obtained."); + } } [return: AllowNull] public object TryGetService(Type serviceType) { if (!Initialized) + { + log.Error("ExportProvider is not initialized, cannot add service."); return null; + } if (!initializingLogging && log.Factory.Configuration == null) { @@ -118,7 +124,19 @@ public object TryGetService(Type serviceType) if (instance != null) return instance; - instance = AddToDisposables(ExportProvider.GetExportedValues(contract).FirstOrDefault(x => contract.StartsWith("github.", StringComparison.OrdinalIgnoreCase) ? x.GetType().Assembly.GetName().Version == currentVersion : true)); + instance = TryGetServiceOnMainThread(serviceType, contract); + if (instance == null) + { + // we need to log these things + } + return instance; + } + + async Task TryGetServiceOnMainThread(Type serviceType, string contract) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + var instance = AddToDisposables(ExportProvider.GetExportedValues(contract).FirstOrDefault(x => contract.StartsWith("github.", StringComparison.OrdinalIgnoreCase) ? x.GetType().Assembly.GetName().Version == currentVersion : true)); if (instance != null) return instance; @@ -127,12 +145,9 @@ public object TryGetService(Type serviceType) if (instance != null) return instance; - if (GitServiceProvider != null) - { - instance = GitServiceProvider.GetService(serviceType); - if (instance != null) - return instance; - } + instance = GitServiceProvider?.GetService(serviceType); + if (instance != null) + return instance; return null; } diff --git a/src/GitHub.VisualStudio/Services/UsageTracker.cs b/src/GitHub.VisualStudio/Services/UsageTracker.cs index 116a3b0c41..b166b2c7fc 100644 --- a/src/GitHub.VisualStudio/Services/UsageTracker.cs +++ b/src/GitHub.VisualStudio/Services/UsageTracker.cs @@ -182,7 +182,17 @@ void RunTimer() timer.Start(); } - async void TimerTick(object sender, EventArgs e) + void TimerTick(object sender, EventArgs e) + { + TimerTick() + .Catch(ex => + { + //log.Warn("Failed submitting usage data", ex); + }) + .Forget(); + } + + async Task TimerTick() { Debug.Assert(client != null, "TimerTick should not be triggered when there is no IMetricsService"); @@ -204,30 +214,23 @@ async void TimerTick(object sender, EventArgs e) if (!userSettings.CollectMetrics) return; - try - { - // Every time we increment the launch count we increment both daily and weekly - // launch count but we only submit (and clear) the weekly launch count when we've - // transitioned into a new week. We've defined a week by the ISO8601 definition, - // i.e. week starting on Monday and ending on Sunday. - var usage = LoadUsage(); - var lastDate = usage.LastUpdated; - var currentDate = DateTimeOffset.Now; - var includeWeekly = GetIso8601WeekOfYear(lastDate) != GetIso8601WeekOfYear(currentDate); - var includeMonthly = lastDate.Month != currentDate.Month; - - // Only send stats once a day. - if (lastDate.Date != currentDate.Date) - { - await SendUsage(usage.Model, includeWeekly, includeMonthly); - ClearCounters(usage.Model, includeWeekly, includeMonthly); - usage.LastUpdated = DateTimeOffset.Now.UtcDateTime; - SaveUsage(usage); - } - } - catch //(Exception ex) + // Every time we increment the launch count we increment both daily and weekly + // launch count but we only submit (and clear) the weekly launch count when we've + // transitioned into a new week. We've defined a week by the ISO8601 definition, + // i.e. week starting on Monday and ending on Sunday. + var usage = LoadUsage(); + var lastDate = usage.LastUpdated; + var currentDate = DateTimeOffset.Now; + var includeWeekly = GetIso8601WeekOfYear(lastDate) != GetIso8601WeekOfYear(currentDate); + var includeMonthly = lastDate.Month != currentDate.Month; + + // Only send stats once a day. + if (lastDate.Date != currentDate.Date) { - //log.Warn("Failed submitting usage data", ex); + await SendUsage(usage.Model, includeWeekly, includeMonthly); + ClearCounters(usage.Model, includeWeekly, includeMonthly); + usage.LastUpdated = DateTimeOffset.Now.UtcDateTime; + SaveUsage(usage); } } diff --git a/src/GitHub.VisualStudio/Settings/generated/PackageSettingsGen.cs b/src/GitHub.VisualStudio/Settings/generated/PackageSettingsGen.cs new file mode 100644 index 0000000000..cb6aedb8f7 --- /dev/null +++ b/src/GitHub.VisualStudio/Settings/generated/PackageSettingsGen.cs @@ -0,0 +1,57 @@ +// This is an automatically generated file, based on settings.json and PackageSettingsGen.tt +/* settings.json content: +{ + "settings": [ + { + "name": "CollectMetrics", + "type": "bool", + "default": 'true' + }, + { + "name": "UIState", + "type": "object", + "typename": "UIState", + "default": 'null' + } + ] +} +*/ + +using GitHub.Settings; +using GitHub.Primitives; +using GitHub.VisualStudio.Helpers; + +namespace GitHub.VisualStudio.Settings { + + public partial class PackageSettings : NotificationAwareObject, IPackageSettings + { + + bool collectMetrics; + public bool CollectMetrics + { + get { return collectMetrics; } + set { collectMetrics = value; this.RaisePropertyChange(); } + } + + UIState uIState; + public UIState UIState + { + get { return uIState; } + set { uIState = value; this.RaisePropertyChange(); } + } + + + void LoadSettings() + { + CollectMetrics = (bool)settingsStore.Read("CollectMetrics", true); + UIState = SimpleJson.DeserializeObject((string)settingsStore.Read("UIState", "{}")); + } + + void SaveSettings() + { + settingsStore.Write("CollectMetrics", CollectMetrics); + settingsStore.Write("UIState", SimpleJson.SerializeObject(UIState)); + } + + } +} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/UI/Views/GitHubPaneViewModel.cs b/src/GitHub.VisualStudio/UI/Views/GitHubPaneViewModel.cs index 6efe22c9f6..605c670334 100644 --- a/src/GitHub.VisualStudio/UI/Views/GitHubPaneViewModel.cs +++ b/src/GitHub.VisualStudio/UI/Views/GitHubPaneViewModel.cs @@ -225,7 +225,7 @@ void StartFlow(UIControllerFlow controllerFlow, [AllowNull]IConnection conn, Vie break; } - var uiProvider = ServiceProvider.GetExportedValue(); + var uiProvider = ServiceProvider.GetService(); var factory = uiProvider.GetService(); var uiflow = factory.UIControllerFactory.CreateExport(); disposables.Add(uiflow); diff --git a/submodules/externalpackages/StartPage b/submodules/externalpackages/StartPage index 0115855168..c41c900b6f 160000 --- a/submodules/externalpackages/StartPage +++ b/submodules/externalpackages/StartPage @@ -1 +1 @@ -Subproject commit 01158551686845eaf3cd3298aabc48a2a6fde90d +Subproject commit c41c900b6f90245187a4b6d8335f85ae70a57462 From 648bb5ffb9df4163290e54b56143411aa431acc2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 31 Oct 2016 12:21:34 +0100 Subject: [PATCH 10/68] Add Expression.Blend.Sdk.WPF package. And use `System.Windows.Interactivity` from there as previously we were assuming that Blend was installed. --- src/GitHub.UI/GitHub.UI.csproj | 9 ++++++++- src/GitHub.UI/packages.config | 1 + src/GitHub.VisualStudio/GitHub.VisualStudio.csproj | 9 ++++++++- src/GitHub.VisualStudio/packages.config | 1 + 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/GitHub.UI/GitHub.UI.csproj b/src/GitHub.UI/GitHub.UI.csproj index 0f027c6d5f..a38b4f8a1a 100644 --- a/src/GitHub.UI/GitHub.UI.csproj +++ b/src/GitHub.UI/GitHub.UI.csproj @@ -59,6 +59,10 @@ false + + ..\..\packages\Expression.Blend.Sdk.WPF.1.0.1\lib\net45\Microsoft.Expression.Interactions.dll + True + ..\..\packages\NullGuard.Fody.1.4.6\Lib\dotnet\NullGuard.dll @@ -68,7 +72,10 @@ - + + ..\..\packages\Expression.Blend.Sdk.WPF.1.0.1\lib\net45\System.Windows.Interactivity.dll + True + diff --git a/src/GitHub.UI/packages.config b/src/GitHub.UI/packages.config index c32824568c..6992a7b202 100644 --- a/src/GitHub.UI/packages.config +++ b/src/GitHub.UI/packages.config @@ -1,5 +1,6 @@  + \ No newline at end of file diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj index f291bdf4ce..d3bc9dd249 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj @@ -108,6 +108,10 @@ True + + ..\..\packages\Expression.Blend.Sdk.WPF.1.0.1\lib\net45\Microsoft.Expression.Interactions.dll + True + ..\..\packages\Microsoft.VisualStudio.CoreUtility.14.3.25407\lib\net45\Microsoft.VisualStudio.CoreUtility.dll True @@ -258,7 +262,10 @@ True - + + ..\..\packages\Expression.Blend.Sdk.WPF.1.0.1\lib\net45\System.Windows.Interactivity.dll + True + diff --git a/src/GitHub.VisualStudio/packages.config b/src/GitHub.VisualStudio/packages.config index ed251bf24e..ec91784d8a 100644 --- a/src/GitHub.VisualStudio/packages.config +++ b/src/GitHub.VisualStudio/packages.config @@ -1,6 +1,7 @@  + From 83d6983c38ec715cb61953300577e912e1824406 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 31 Oct 2016 15:56:30 +0100 Subject: [PATCH 11/68] Added package prerequisites. I couldn't work out which ones we *actually* need (we don't need anything beyond Team Explorer, right? That doesn't seem to be one of the choices) but you have to put at least one in there so I chose the core editor. --- src/GitHub.VisualStudio/source.extension.vsixmanifest | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/GitHub.VisualStudio/source.extension.vsixmanifest b/src/GitHub.VisualStudio/source.extension.vsixmanifest index 6257ace21d..6d283481ec 100644 --- a/src/GitHub.VisualStudio/source.extension.vsixmanifest +++ b/src/GitHub.VisualStudio/source.extension.vsixmanifest @@ -11,7 +11,7 @@ GitHub;git;open source;source control;branch;pull request;team explorer;commit;publish - + @@ -29,4 +29,7 @@ + + + \ No newline at end of file From 8646aa54ae3e56e6ba14d09a828cbd5bec2f91aa Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 1 Nov 2016 14:46:47 +0100 Subject: [PATCH 12/68] Service requests for IMenuProvider. To this had to fix `GetServiceAsync` call in `IUIProvider` which was trying to cast a `Task` to an `IComponentModel`. --- src/GitHub.VisualStudio/GitHubPackage.cs | 5 ++++- src/GitHub.VisualStudio/Services/UIProvider.cs | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/GitHub.VisualStudio/GitHubPackage.cs b/src/GitHub.VisualStudio/GitHubPackage.cs index 30159f3496..df04e1d56a 100644 --- a/src/GitHub.VisualStudio/GitHubPackage.cs +++ b/src/GitHub.VisualStudio/GitHubPackage.cs @@ -103,6 +103,7 @@ public GHClient(IProgram program) [NullGuard.NullGuard(NullGuard.ValidationFlags.None)] [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] + [ProvideService(typeof(IMenuProvider), IsAsyncQueryable = true)] [ProvideService(typeof(IUIProvider), IsAsyncQueryable = true)] [ProvideService(typeof(IUsageTracker), IsAsyncQueryable = true)] [ProvideAutoLoad(UIContextGuids.NoSolution)] @@ -177,7 +178,9 @@ async Task CreateService(IAsyncServiceContainer container, CancellationT if (serviceType == typeof(IUIProvider)) { - return new UIProvider(this); + var result = new UIProvider(this); + await result.Initialize(); + return result; } else if (serviceType == typeof(IMenuProvider)) { diff --git a/src/GitHub.VisualStudio/Services/UIProvider.cs b/src/GitHub.VisualStudio/Services/UIProvider.cs index 1d9e4842c2..bed9805bb7 100644 --- a/src/GitHub.VisualStudio/Services/UIProvider.cs +++ b/src/GitHub.VisualStudio/Services/UIProvider.cs @@ -42,7 +42,7 @@ class OwnedComposablePart readonly Version currentVersion; bool initializingLogging = false; - public ExportProvider ExportProvider { get; } + public ExportProvider ExportProvider { get; private set; } CompositionContainer tempContainer; CompositionContainer TempContainer @@ -72,12 +72,15 @@ public UIProvider([Import(typeof(SVsServiceProvider))] IServiceProvider serviceP this.serviceProvider = serviceProvider; tempParts = new Dictionary(); + } + public async Task Initialize() + { var asyncProvider = serviceProvider as IAsyncServiceProvider; IComponentModel componentModel = null; if (asyncProvider != null) { - componentModel = asyncProvider.GetServiceAsync(typeof(SComponentModel)) as IComponentModel; + componentModel = await asyncProvider.GetServiceAsync(typeof(SComponentModel)) as IComponentModel; } else { From 24f344b0959da5f1c4c31e3bc937d48faaab1f14 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 1 Nov 2016 15:36:55 +0100 Subject: [PATCH 13/68] Bump StartPage to pick up build fix --- submodules/externalpackages/StartPage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/externalpackages/StartPage b/submodules/externalpackages/StartPage index c41c900b6f..b3d41e708a 160000 --- a/submodules/externalpackages/StartPage +++ b/submodules/externalpackages/StartPage @@ -1 +1 @@ -Subproject commit c41c900b6f90245187a4b6d8335f85ae70a57462 +Subproject commit b3d41e708a66e92376fd90dc736637afb900b80d From 4d15c19603ebdf3ec889a9e2c93956da1b68db21 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 2 Nov 2016 13:58:09 +0100 Subject: [PATCH 14/68] Fix the services `IUIProvider` is now a global service and not a MEF component, so we now use a separate class as a MEF wrapper that dispatches calls to the service, to connect the two worlds. --- src/GitHub.VisualStudio/GitHubPackage.cs | 22 ++- .../Services/UIProvider.cs | 168 +++++++++++------- 2 files changed, 120 insertions(+), 70 deletions(-) diff --git a/src/GitHub.VisualStudio/GitHubPackage.cs b/src/GitHub.VisualStudio/GitHubPackage.cs index df04e1d56a..5b79ec45e2 100644 --- a/src/GitHub.VisualStudio/GitHubPackage.cs +++ b/src/GitHub.VisualStudio/GitHubPackage.cs @@ -14,8 +14,6 @@ using System.Threading; using System.Threading.Tasks; using Task = System.Threading.Tasks.Task; -using Microsoft.VisualStudio.Threading; -using Microsoft.VisualStudio.ComponentModelHost; using GitHub.VisualStudio.Menus; namespace GitHub.VisualStudio @@ -52,9 +50,15 @@ public GitHubPackage(IServiceProvider serviceProvider) protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) { await base.InitializeAsync(cancellationToken, progress); + await EnsurePackageLoaded(new Guid(ServiceProviderPackage.ServiceProviderPackageId)); //var usageTracker = await GetServiceAsync(typeof(IUsageTracker)) as IUsageTracker; //usageTracker.IncrementLaunchCount(); + InitializeMenus().Forget(); + } + + async Task InitializeMenus() + { var menus = await GetServiceAsync(typeof(IMenuProvider)) as IMenuProvider; await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); @@ -66,6 +70,16 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke serviceProvider.AddCommandHandler(menu.Guid, menu.CmdId, menu.CanShow, () => menu.Activate()); } + async Task EnsurePackageLoaded(Guid packageGuid) + { + var shell = await GetServiceAsync(typeof(SVsShell)) as IVsShell; + if (shell != null) + { + IVsPackage vsPackage; + ErrorHandler.ThrowOnFailure(shell.LoadPackage(ref packageGuid, out vsPackage)); + } + } + } [Export(typeof(IGitHubClient))] @@ -111,7 +125,7 @@ public GHClient(IProgram program) [Guid(ServiceProviderPackageId)] public sealed class ServiceProviderPackage : AsyncPackage { - const string ServiceProviderPackageId = "D5CE1488-DEDE-426D-9E5B-BFCCFBE33E53"; + public const string ServiceProviderPackageId = "D5CE1488-DEDE-426D-9E5B-BFCCFBE33E53"; const string StartPagePreview4PackageId = "3b764d23-faf7-486f-94c7-b3accc44a70d"; const string StartPagePreview5PackageId = "3b764d23-faf7-486f-94c7-b3accc44a70e"; @@ -178,7 +192,7 @@ async Task CreateService(IAsyncServiceContainer container, CancellationT if (serviceType == typeof(IUIProvider)) { - var result = new UIProvider(this); + var result = new GitHubServiceProvider(this); await result.Initialize(); return result; } diff --git a/src/GitHub.VisualStudio/Services/UIProvider.cs b/src/GitHub.VisualStudio/Services/UIProvider.cs index bed9805bb7..552b345d0c 100644 --- a/src/GitHub.VisualStudio/Services/UIProvider.cs +++ b/src/GitHub.VisualStudio/Services/UIProvider.cs @@ -17,16 +17,104 @@ using Microsoft.VisualStudio.Shell; using NLog; using NullGuard; -using System.Threading; -using System.Threading.Tasks; using Task = System.Threading.Tasks.Task; -using Microsoft.VisualStudio.Threading; namespace GitHub.VisualStudio { + /// + /// This is a thin MEF wrapper around the GitHubServiceProvider + /// which is registered as a global VS service. This class just + /// redirects every request to the actual service, and can be + /// thrown away as soon as the caller is done (no state is kept) + /// [Export(typeof(IUIProvider))] - [PartCreationPolicy(CreationPolicy.Shared)] - public class UIProvider : IUIProvider, IDisposable + [PartCreationPolicy(CreationPolicy.NonShared)] + public class GitHubProviderDispatcher : IUIProvider + { + readonly IUIProvider theRealProvider; + + [ImportingConstructor] + public GitHubProviderDispatcher([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) + { + theRealProvider = serviceProvider.GetService(typeof(IUIProvider)) as IUIProvider; + } + + public ExportProvider ExportProvider { get { return theRealProvider.ExportProvider; } } + + public IServiceProvider GitServiceProvider + { + get + { + return theRealProvider.GitServiceProvider; + } + + set + { + theRealProvider.GitServiceProvider = value; + } + } + + public void AddService(Type t, object owner, object instance) + { + theRealProvider.AddService(t, owner, instance); + } + + public void AddService(object owner, T instance) + { + theRealProvider.AddService(owner, instance); + } + + public object GetService(Type serviceType) + { + return theRealProvider.GetService(serviceType); + } + + public IObservable ListenToCompletionState() + { + return theRealProvider.ListenToCompletionState(); + } + + public void RemoveService(Type t, object owner) + { + theRealProvider.RemoveService(t, owner); + } + + public void RunUI() + { + theRealProvider.RunUI(); + } + + public void RunUI(UIControllerFlow controllerFlow, IConnection connection) + { + theRealProvider.RunUI(controllerFlow, connection); + } + + public IObservable SetupUI(UIControllerFlow controllerFlow, IConnection connection) + { + return theRealProvider.SetupUI(controllerFlow, connection); + } + + public object TryGetService(string typename) + { + return theRealProvider.TryGetService(typename); + } + + public object TryGetService(Type t) + { + return theRealProvider.TryGetService(t); + } + + public T TryGetService() where T : class + { + return theRealProvider.TryGetService(); + } + } + + /// + /// This is a globally registered service (see `GitHubPackage`). + /// If you need to access this service via MEF, use the `IUIProvider` type + /// + internal class GitHubServiceProvider : IUIProvider, IDisposable { class OwnedComposablePart { @@ -63,12 +151,9 @@ CompositionContainer TempContainer [AllowNull] public IServiceProvider GitServiceProvider { get; set; } - public bool Initialized { get { return ExportProvider != null; } } - - [ImportingConstructor] - public UIProvider([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) + public GitHubServiceProvider(IServiceProvider serviceProvider) { - this.currentVersion = typeof(UIProvider).Assembly.GetName().Version; + this.currentVersion = this.GetType().Assembly.GetName().Version; this.serviceProvider = serviceProvider; tempParts = new Dictionary(); @@ -82,15 +167,17 @@ public async Task Initialize() { componentModel = await asyncProvider.GetServiceAsync(typeof(SComponentModel)) as IComponentModel; } + else { - componentModel = serviceProvider.GetService(typeof(SComponentModel)) as IComponentModel; + componentModel = serviceProvider?.GetService(typeof(SComponentModel)) as IComponentModel; } Debug.Assert(componentModel != null, "Service of type SComponentModel not found"); if (componentModel == null) { log.Error("Service of type SComponentModel not found"); + return; } ExportProvider = componentModel.DefaultExportProvider; @@ -103,12 +190,6 @@ public async Task Initialize() [return: AllowNull] public object TryGetService(Type serviceType) { - if (!Initialized) - { - log.Error("ExportProvider is not initialized, cannot add service."); - return null; - } - if (!initializingLogging && log.Factory.Configuration == null) { initializingLogging = true; @@ -119,6 +200,9 @@ public object TryGetService(Type serviceType) } catch { +#if DEBUG + throw; +#endif } } @@ -127,19 +211,7 @@ public object TryGetService(Type serviceType) if (instance != null) return instance; - instance = TryGetServiceOnMainThread(serviceType, contract); - if (instance == null) - { - // we need to log these things - } - return instance; - } - - async Task TryGetServiceOnMainThread(Type serviceType, string contract) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - var instance = AddToDisposables(ExportProvider.GetExportedValues(contract).FirstOrDefault(x => contract.StartsWith("github.", StringComparison.OrdinalIgnoreCase) ? x.GetType().Assembly.GetName().Version == currentVersion : true)); + instance = AddToDisposables(ExportProvider.GetExportedValues(contract).FirstOrDefault(x => contract.StartsWith("github.", StringComparison.OrdinalIgnoreCase) ? x.GetType().Assembly.GetName().Version == currentVersion : true)); if (instance != null) return instance; @@ -197,12 +269,6 @@ public void AddService(object owner, T instance) public void AddService(Type t, object owner, object instance) { - if (!Initialized) - { - log.Error("ExportProvider is not initialized, cannot add service."); - return; - } - string contract = AttributedModelServices.GetContractName(t); Debug.Assert(!string.IsNullOrEmpty(contract), "Every type must have a contract name"); @@ -224,12 +290,6 @@ public void AddService(Type t, object owner, object instance) /// or if it's null, the service will be removed without checking for ownership public void RemoveService(Type t, [AllowNull] object owner) { - if (!Initialized) - { - log.Error("ExportProvider is not initialized, cannot remove service."); - return; - } - string contract = AttributedModelServices.GetContractName(t); Debug.Assert(!string.IsNullOrEmpty(contract), "Every type must have a contract name"); @@ -248,12 +308,6 @@ public void RemoveService(Type t, [AllowNull] object owner) UI.WindowController windowController; public IObservable SetupUI(UIControllerFlow controllerFlow, [AllowNull] IConnection connection) { - if (!Initialized) - { - log.Error("ExportProvider is not initialized, cannot setup UI."); - return Observable.Empty(); - } - StopUI(); var factory = TryGetService(typeof(IExportFactoryProvider)) as IExportFactoryProvider; @@ -292,12 +346,6 @@ public IObservable ListenToCompletionState() public void RunUI() { - if (!Initialized) - { - log.Error("ExportProvider is not initialized, cannot run UI."); - return; - } - Debug.Assert(windowController != null, "WindowController is null, did you forget to call SetupUI?"); if (windowController == null) { @@ -316,12 +364,6 @@ public void RunUI() public void RunUI(UIControllerFlow controllerFlow, [AllowNull] IConnection connection) { - if (!Initialized) - { - log.Error("ExportProvider is not initialized, cannot run UI for {0}.", controllerFlow); - return; - } - SetupUI(controllerFlow, connection); try { @@ -335,12 +377,6 @@ public void RunUI(UIControllerFlow controllerFlow, [AllowNull] IConnection conne public void StopUI() { - if (!Initialized) - { - log.Error("ExportProvider is not initialized, cannot stop UI."); - return; - } - StopUI(currentUIFlow); currentUIFlow = null; } From 7214fcf8c4112cf569ae7127cff7dccfd1f76bed Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 3 Nov 2016 13:30:51 +0100 Subject: [PATCH 15/68] Pick up StartPage build fix --- submodules/externalpackages/StartPage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/externalpackages/StartPage b/submodules/externalpackages/StartPage index b3d41e708a..e4385024a7 160000 --- a/submodules/externalpackages/StartPage +++ b/submodules/externalpackages/StartPage @@ -1 +1 @@ -Subproject commit b3d41e708a66e92376fd90dc736637afb900b80d +Subproject commit e4385024a78921e5f9a2abf679334861c43ac91d From 1eee8035dd83a0cb3fe3b0059ac23ab06df09734 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 3 Nov 2016 14:09:55 +0100 Subject: [PATCH 16/68] Added dependency on start page. Added dependency on GitHub.StartPage.Preview5 from GitHub.VisualStudio --- GitHubVS.sln | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/GitHubVS.sln b/GitHubVS.sln index f8dac70cbe..e966b03dea 100644 --- a/GitHubVS.sln +++ b/GitHubVS.sln @@ -1,9 +1,12 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.25807.0 +VisualStudioVersion = 15.0.25824.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.VisualStudio", "src\GitHub.VisualStudio\GitHub.VisualStudio.csproj", "{11569514-5AE5-4B5B-92A2-F10B0967DE5F}" + ProjectSection(ProjectDependencies) = postProject + {1BC94B6A-B021-4207-A70E-936EE272AD3E} = {1BC94B6A-B021-4207-A70E-936EE272AD3E} + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Meta", "Meta", "{72036B62-2FA6-4A22-8B33-69F698A18CF1}" ProjectSection(SolutionItems) = preProject From b6c850616132b21e514136009c7f291898b810cc Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 3 Nov 2016 15:25:41 +0100 Subject: [PATCH 17/68] Make PullRequests TE button appear and work. Needed an MEF-visible `IMenuProvider` (`MenuProviderDispatcher`) and the `ExportMenu` attribute to be re-applied to `OpenPullRequests` in order to find the menu. --- src/GitHub.VisualStudio/Menus/MenuProvider.cs | 27 ++++++++++++++++++- .../Menus/OpenPullRequests.cs | 1 + 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/GitHub.VisualStudio/Menus/MenuProvider.cs b/src/GitHub.VisualStudio/Menus/MenuProvider.cs index 1e0d49ca2e..01eb7f7182 100644 --- a/src/GitHub.VisualStudio/Menus/MenuProvider.cs +++ b/src/GitHub.VisualStudio/Menus/MenuProvider.cs @@ -3,10 +3,35 @@ using System.Collections.ObjectModel; using System.ComponentModel.Composition; using System.Linq; +using System; +using Microsoft.VisualStudio.Shell; namespace GitHub.VisualStudio.Menus { - public class MenuProvider : IMenuProvider + /// + /// This is a thin MEF wrapper around the MenuProvider + /// which is registered as a global VS service. This class just + /// redirects every request to the actual service, and can be + /// thrown away as soon as the caller is done (no state is kept) + /// + [Export(typeof(IMenuProvider))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class MenuProviderDispatcher : IMenuProvider + { + readonly IMenuProvider theRealProvider; + + [ImportingConstructor] + public MenuProviderDispatcher([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) + { + theRealProvider = serviceProvider.GetService(typeof(IMenuProvider)) as IMenuProvider; + } + + public IReadOnlyCollection DynamicMenus => theRealProvider.DynamicMenus; + + public IReadOnlyCollection Menus => theRealProvider.Menus; + } + + internal class MenuProvider : IMenuProvider { public IReadOnlyCollection Menus { get; } diff --git a/src/GitHub.VisualStudio/Menus/OpenPullRequests.cs b/src/GitHub.VisualStudio/Menus/OpenPullRequests.cs index 557e560d38..3d0dcd4c3a 100644 --- a/src/GitHub.VisualStudio/Menus/OpenPullRequests.cs +++ b/src/GitHub.VisualStudio/Menus/OpenPullRequests.cs @@ -6,6 +6,7 @@ namespace GitHub.VisualStudio.Menus { + [ExportMenu(MenuType = MenuType.OpenPullRequests)] public class OpenPullRequests : MenuBase, IMenuHandler { public Guid Guid => GuidList.guidGitHubCmdSet; From 7113a15e27bb33750eb5babe3e3ee7f991abb1c7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 3 Nov 2016 15:51:46 +0100 Subject: [PATCH 18/68] We don't need to load the start page packages. Visual Studio picks them up automatically. --- src/GitHub.VisualStudio/GitHubPackage.cs | 27 ++---------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/src/GitHub.VisualStudio/GitHubPackage.cs b/src/GitHub.VisualStudio/GitHubPackage.cs index 5b79ec45e2..7d96b837cc 100644 --- a/src/GitHub.VisualStudio/GitHubPackage.cs +++ b/src/GitHub.VisualStudio/GitHubPackage.cs @@ -154,35 +154,12 @@ Version VSVersion } } - protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) + protected override Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) { AddService(typeof(IUIProvider), CreateService, true); AddService(typeof(IUsageTracker), CreateService, true); AddService(typeof(IMenuProvider), CreateService, true); - - if (VSVersion.Major == 15) - { - // Load the start page package only for Dev15 Preview 4 - if (VSVersion.Build == 25618) - { - var shell = await GetServiceAsync(typeof(SVsShell)) as IVsShell; - IVsPackage startPagePackage; - if (ErrorHandler.Failed(shell?.LoadPackage(new Guid(StartPagePreview4PackageId), out startPagePackage) ?? -1)) - { - // ¯\_(ツ)_/¯ - } - } - // Load the start page package only for Dev15 Preview 5 - else if (VSVersion.Build == 25807) - { - var shell = await GetServiceAsync(typeof(SVsShell)) as IVsShell; - IVsPackage startPagePackage; - if (ErrorHandler.Failed(shell?.LoadPackage(new Guid(StartPagePreview5PackageId), out startPagePackage) ?? -1)) - { - // ¯\_(ツ)_/¯ - } - } - } + return Task.FromResult(null); } async Task CreateService(IAsyncServiceContainer container, CancellationToken cancellationToken, Type serviceType) From 44dc4f5078b3cbc4621f5d7e694dbfafd6daaeea Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 3 Nov 2016 20:04:54 +0100 Subject: [PATCH 19/68] Updated StartPage submodule. --- submodules/externalpackages/StartPage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/externalpackages/StartPage b/submodules/externalpackages/StartPage index e4385024a7..a2cd5ea267 160000 --- a/submodules/externalpackages/StartPage +++ b/submodules/externalpackages/StartPage @@ -1 +1 @@ -Subproject commit e4385024a78921e5f9a2abf679334861c43ac91d +Subproject commit a2cd5ea267d6247549876531584178f3b069ce19 From 5710f1d00f7bb43d95912a24e7c520fbe88d8882 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Mon, 7 Nov 2016 12:47:48 +0100 Subject: [PATCH 20/68] Fix visibility for tests --- src/GitHub.VisualStudio/Services/UIProvider.cs | 2 +- src/UnitTests/GitHub.App/Controllers/UIProviderTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GitHub.VisualStudio/Services/UIProvider.cs b/src/GitHub.VisualStudio/Services/UIProvider.cs index 552b345d0c..23d6148dda 100644 --- a/src/GitHub.VisualStudio/Services/UIProvider.cs +++ b/src/GitHub.VisualStudio/Services/UIProvider.cs @@ -114,7 +114,7 @@ public T TryGetService() where T : class /// This is a globally registered service (see `GitHubPackage`). /// If you need to access this service via MEF, use the `IUIProvider` type /// - internal class GitHubServiceProvider : IUIProvider, IDisposable + public class GitHubServiceProvider : IUIProvider, IDisposable { class OwnedComposablePart { diff --git a/src/UnitTests/GitHub.App/Controllers/UIProviderTests.cs b/src/UnitTests/GitHub.App/Controllers/UIProviderTests.cs index 9fa96b5e79..dc5c76a5f4 100644 --- a/src/UnitTests/GitHub.App/Controllers/UIProviderTests.cs +++ b/src/UnitTests/GitHub.App/Controllers/UIProviderTests.cs @@ -23,7 +23,7 @@ public void ListenToCompletionDoesNotThrowInRelease() { var provider = Substitutes.GetFullyMockedServiceProvider(); - using (var p = new UIProvider(provider)) + using (var p = new GitHubServiceProvider(provider)) { #if DEBUG Assert.ThrowsAny(() => From 79b7f32b3fbe9e8e2e7158393a03800dc260f777 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Mon, 7 Nov 2016 13:51:43 +0100 Subject: [PATCH 21/68] These services were not written for nullguard --- src/GitHub.TeamFoundation.14/Services/TeamExplorerServices.cs | 1 + src/GitHub.TeamFoundation.14/Services/VSGitServices.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/GitHub.TeamFoundation.14/Services/TeamExplorerServices.cs b/src/GitHub.TeamFoundation.14/Services/TeamExplorerServices.cs index 51739d75ef..44847542ef 100644 --- a/src/GitHub.TeamFoundation.14/Services/TeamExplorerServices.cs +++ b/src/GitHub.TeamFoundation.14/Services/TeamExplorerServices.cs @@ -8,6 +8,7 @@ namespace GitHub.Services { + [NullGuard.NullGuard(NullGuard.ValidationFlags.None)] [Export(typeof(ITeamExplorerServices))] [PartCreationPolicy(CreationPolicy.Shared)] public class TeamExplorerServices : ITeamExplorerServices diff --git a/src/GitHub.TeamFoundation.14/Services/VSGitServices.cs b/src/GitHub.TeamFoundation.14/Services/VSGitServices.cs index a6ed911e55..896097cd17 100644 --- a/src/GitHub.TeamFoundation.14/Services/VSGitServices.cs +++ b/src/GitHub.TeamFoundation.14/Services/VSGitServices.cs @@ -15,6 +15,7 @@ namespace GitHub.Services { + [NullGuard.NullGuard(NullGuard.ValidationFlags.None)] [Export(typeof(IVSGitServices))] [PartCreationPolicy(CreationPolicy.Shared)] public class VSGitServices : IVSGitServices From 8c89ef013347c2e4d16d8a0dcbbe734288498a90 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Mon, 7 Nov 2016 13:52:00 +0100 Subject: [PATCH 22/68] Implement a variant of the clone repo dialog for re-cloning from the Start Page The Start Page re-acquisition code path needs to clone repositories, but it already knows which repo to clone, so we need a variant of the clone dialog for this purpose. --- src/GitHub.App/Controllers/UIController.cs | 19 +++++++++++++++++++ src/GitHub.App/GitHub.App.csproj | 1 + src/GitHub.App/SampleData/SampleViewModels.cs | 2 +- .../ViewModels/RepositoryCloneViewModel.cs | 4 ++-- .../GitHub.Exports.Reactive.csproj | 1 + .../ViewModels/IRepositoryCloneViewModel.cs | 8 +------- src/GitHub.Exports/Exports/ExportMetadata.cs | 2 ++ src/GitHub.Exports/UI/IUIController.cs | 3 ++- .../GitHub.VisualStudio.csproj | 7 +++++++ submodules/externalpackages/StartPage | 2 +- 10 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/GitHub.App/Controllers/UIController.cs b/src/GitHub.App/Controllers/UIController.cs index 964ec35775..5711830f9a 100644 --- a/src/GitHub.App/Controllers/UIController.cs +++ b/src/GitHub.App/Controllers/UIController.cs @@ -366,6 +366,12 @@ void ConfigureUIHandlingStates() .PermitDynamic(Trigger.Cancel, () => Go(Trigger.Cancel)) .PermitDynamic(Trigger.Finish, () => Go(Trigger.Finish)); + uiStateMachine.Configure(UIViewType.StartPageClone) + .OnEntry(tr => RunView(UIViewType.StartPageClone, CalculateDirection(tr))) + .PermitDynamic(Trigger.Next, () => Go(Trigger.Next)) + .PermitDynamic(Trigger.Cancel, () => Go(Trigger.Cancel)) + .PermitDynamic(Trigger.Finish, () => Go(Trigger.Finish)); + uiStateMachine.Configure(UIViewType.End) .OnEntryFrom(Trigger.Cancel, () => End(false)) .OnEntryFrom(Trigger.Next, () => End(true)) @@ -558,6 +564,19 @@ void ConfigureLogicStates() logic.Configure(UIViewType.End) .Permit(Trigger.Next, UIViewType.None); machines.Add(UIControllerFlow.LogoutRequired, logic); + + // start page clone flow + logic = new StateMachine(UIViewType.None); + logic.Configure(UIViewType.None) + .Permit(Trigger.Next, UIViewType.StartPageClone) + .Permit(Trigger.Finish, UIViewType.End); + logic.Configure(UIViewType.StartPageClone) + .Permit(Trigger.Next, UIViewType.End) + .Permit(Trigger.Cancel, UIViewType.End) + .Permit(Trigger.Finish, UIViewType.End); + logic.Configure(UIViewType.End) + .Permit(Trigger.Next, UIViewType.None); + machines.Add(UIControllerFlow.StartPageClone, logic); } UIControllerFlow SelectActiveFlow() diff --git a/src/GitHub.App/GitHub.App.csproj b/src/GitHub.App/GitHub.App.csproj index 3fbd64a88f..d4ab4075aa 100644 --- a/src/GitHub.App/GitHub.App.csproj +++ b/src/GitHub.App/GitHub.App.csproj @@ -217,6 +217,7 @@ + diff --git a/src/GitHub.App/SampleData/SampleViewModels.cs b/src/GitHub.App/SampleData/SampleViewModels.cs index 62861efac2..aa2510e905 100644 --- a/src/GitHub.App/SampleData/SampleViewModels.cs +++ b/src/GitHub.App/SampleData/SampleViewModels.cs @@ -359,7 +359,7 @@ public IReactiveCommand CloneCommand private set; } - public IRepositoryModel SelectedRepository { get; set; } + public ISimpleRepositoryModel SelectedRepository { get; set; } public ObservableCollection Repositories { diff --git a/src/GitHub.App/ViewModels/RepositoryCloneViewModel.cs b/src/GitHub.App/ViewModels/RepositoryCloneViewModel.cs index c5ae9ffac9..27e2bd7926 100644 --- a/src/GitHub.App/ViewModels/RepositoryCloneViewModel.cs +++ b/src/GitHub.App/ViewModels/RepositoryCloneViewModel.cs @@ -279,12 +279,12 @@ public ObservableCollection Repositories private set { this.RaiseAndSetIfChanged(ref repositories, (TrackingCollection)value); } } - IRepositoryModel selectedRepository; + ISimpleRepositoryModel selectedRepository; /// /// Selected repository to clone /// [AllowNull] - public IRepositoryModel SelectedRepository + public ISimpleRepositoryModel SelectedRepository { [return: AllowNull] get { return selectedRepository; } diff --git a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj index 6a5ce8e477..8bbc173555 100644 --- a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj +++ b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj @@ -117,6 +117,7 @@ + diff --git a/src/GitHub.Exports.Reactive/ViewModels/IRepositoryCloneViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/IRepositoryCloneViewModel.cs index e166a014c0..b3708a21ce 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/IRepositoryCloneViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/IRepositoryCloneViewModel.cs @@ -9,20 +9,14 @@ namespace GitHub.ViewModels /// /// ViewModel for the the Clone Repository dialog /// - public interface IRepositoryCloneViewModel : IViewModel, IRepositoryCreationTarget + public interface IRepositoryCloneViewModel : IBaseCloneViewModel, IRepositoryCreationTarget { - /// - /// Command to clone the currently selected repository. - /// - IReactiveCommand CloneCommand { get; } /// /// The list of repositories the current user may clone from the specified host. /// ObservableCollection Repositories { get; } - IRepositoryModel SelectedRepository { get; set; } - bool FilterTextIsEnabled { get; } /// diff --git a/src/GitHub.Exports/Exports/ExportMetadata.cs b/src/GitHub.Exports/Exports/ExportMetadata.cs index b9f6a6026d..b26018a379 100644 --- a/src/GitHub.Exports/Exports/ExportMetadata.cs +++ b/src/GitHub.Exports/Exports/ExportMetadata.cs @@ -28,6 +28,8 @@ public enum UIViewType LoggedOut, NotAGitRepository, NotAGitHubRepository, + StartPageClone, + } public enum MenuType diff --git a/src/GitHub.Exports/UI/IUIController.cs b/src/GitHub.Exports/UI/IUIController.cs index fe8e1441c4..403477658d 100644 --- a/src/GitHub.Exports/UI/IUIController.cs +++ b/src/GitHub.Exports/UI/IUIController.cs @@ -31,7 +31,8 @@ public enum UIControllerFlow PullRequests, Gist, LogoutRequired, - Home + Home, + StartPageClone } public class ViewWithData diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj index d3bc9dd249..a1b9ec9e56 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj @@ -326,6 +326,9 @@ OptionsControl.xaml + + StartPageCloneView.xaml + RepositoryCloneControl.xaml @@ -461,6 +464,10 @@ MSBuild:Compile Designer + + MSBuild:Compile + Designer + Designer MSBuild:Compile diff --git a/submodules/externalpackages/StartPage b/submodules/externalpackages/StartPage index a2cd5ea267..f774eeb582 160000 --- a/submodules/externalpackages/StartPage +++ b/submodules/externalpackages/StartPage @@ -1 +1 @@ -Subproject commit a2cd5ea267d6247549876531584178f3b069ce19 +Subproject commit f774eeb58246dd83e46abc4f23a074d6ace44ee5 From 1c561dd7d8954aadba43d62907fb26291eec9d75 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Mon, 7 Nov 2016 14:32:17 +0100 Subject: [PATCH 23/68] Add missing files --- .../ViewModels/StartPageCloneViewModel.cs | 208 +++++++++++ .../ViewModels/IBaseCloneViewModel.cs | 21 ++ .../UI/Views/Controls/StartPageCloneView.xaml | 329 ++++++++++++++++++ .../Views/Controls/StartPageCloneView.xaml.cs | 49 +++ 4 files changed, 607 insertions(+) create mode 100644 src/GitHub.App/ViewModels/StartPageCloneViewModel.cs create mode 100644 src/GitHub.Exports.Reactive/ViewModels/IBaseCloneViewModel.cs create mode 100644 src/GitHub.VisualStudio/UI/Views/Controls/StartPageCloneView.xaml create mode 100644 src/GitHub.VisualStudio/UI/Views/Controls/StartPageCloneView.xaml.cs diff --git a/src/GitHub.App/ViewModels/StartPageCloneViewModel.cs b/src/GitHub.App/ViewModels/StartPageCloneViewModel.cs new file mode 100644 index 0000000000..15baf45480 --- /dev/null +++ b/src/GitHub.App/ViewModels/StartPageCloneViewModel.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Text.RegularExpressions; +using System.Windows.Input; +using GitHub.App; +using GitHub.Exports; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.Services; +using GitHub.Validation; +using NLog; +using NullGuard; +using ReactiveUI; +using Rothko; +using System.Collections.ObjectModel; +using GitHub.Collections; +using GitHub.UI; +using GitHub.Extensions.Reactive; + +namespace GitHub.ViewModels +{ + [ExportViewModel(ViewType=UIViewType.StartPageClone)] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class StartPageCloneViewModel : BaseViewModel, IBaseCloneViewModel + { + static readonly Logger log = LogManager.GetCurrentClassLogger(); + + readonly IRepositoryCloneService cloneService; + readonly IOperatingSystem operatingSystem; + readonly INotificationService notificationService; + readonly IUsageTracker usageTracker; + readonly ReactiveCommand browseForDirectoryCommand = ReactiveCommand.Create(); + readonly ObservableAsPropertyHelper canClone; + string baseRepositoryPath; + + [ImportingConstructor] + StartPageCloneViewModel( + IConnectionRepositoryHostMap connectionRepositoryHostMap, + IRepositoryCloneService repositoryCloneService, + IOperatingSystem operatingSystem, + INotificationService notificationService, + IUsageTracker usageTracker) + : this(connectionRepositoryHostMap.CurrentRepositoryHost, repositoryCloneService, operatingSystem, notificationService, usageTracker) + { } + + public StartPageCloneViewModel( + IRepositoryHost repositoryHost, + IRepositoryCloneService cloneService, + IOperatingSystem operatingSystem, + INotificationService notificationService, + IUsageTracker usageTracker) + { + this.cloneService = cloneService; + this.operatingSystem = operatingSystem; + this.notificationService = notificationService; + this.usageTracker = usageTracker; + + Title = string.Format(CultureInfo.CurrentCulture, Resources.CloneTitle, repositoryHost.Title); + + var baseRepositoryPath = this.WhenAnyValue(x => x.BaseRepositoryPath); + + BaseRepositoryPathValidator = ReactivePropertyValidator.ForObservable(baseRepositoryPath) + .IfNullOrEmpty(Resources.RepositoryCreationClonePathEmpty) + .IfTrue(x => x.Length > 200, Resources.RepositoryCreationClonePathTooLong) + .IfContainsInvalidPathChars(Resources.RepositoryCreationClonePathInvalidCharacters) + .IfPathNotRooted(Resources.RepositoryCreationClonePathInvalid) + .IfTrue(IsAlreadyRepoAtPath, Resources.RepositoryNameValidatorAlreadyExists); + + var canCloneObservable = this.WhenAny( + x => x.SelectedRepository, + x => x.BaseRepositoryPathValidator.ValidationResult.IsValid, + (x, y) => x.Value != null && y.Value); + canClone = canCloneObservable.ToProperty(this, x => x.CanClone); + CloneCommand = ReactiveCommand.CreateAsyncObservable(canCloneObservable, OnCloneRepository); + + browseForDirectoryCommand.Subscribe(_ => ShowBrowseForDirectoryDialog()); + this.WhenAny(x => x.BaseRepositoryPathValidator.ValidationResult, x => x.Value) + .Subscribe(); + BaseRepositoryPath = cloneService.DefaultClonePath; + } + + + IObservable OnCloneRepository(object state) + { + return Observable.Start(() => + { + var repository = SelectedRepository; + Debug.Assert(repository != null, "Should not be able to attempt to clone a repo when it's null"); + if (repository == null) + { + notificationService.ShowError(Resources.RepositoryCloneFailedNoSelectedRepo); + return Observable.Return(Unit.Default); + } + + // The following is a noop if the directory already exists. + operatingSystem.Directory.CreateDirectory(BaseRepositoryPath); + + return cloneService.CloneRepository(repository.CloneUrl, repository.Name, BaseRepositoryPath) + .ContinueAfter(() => + { + usageTracker.IncrementCloneCount(); + return Observable.Return(Unit.Default); + }); + }) + .SelectMany(_ => _) + .Catch(e => + { + var repository = SelectedRepository; + Debug.Assert(repository != null, "Should not be able to attempt to clone a repo when it's null"); + notificationService.ShowError(e.GetUserFriendlyErrorMessage(ErrorType.ClonedFailed, repository.Name)); + return Observable.Return(Unit.Default); + }); + } + + bool IsAlreadyRepoAtPath(string path) + { + Debug.Assert(path != null, "RepositoryCloneViewModel.IsAlreadyRepoAtPath cannot be passed null as a path parameter."); + + bool isAlreadyRepoAtPath = false; + + if (SelectedRepository != null) + { + string potentialPath = Path.Combine(path, SelectedRepository.Name); + isAlreadyRepoAtPath = operatingSystem.Directory.Exists(potentialPath); + } + + return isAlreadyRepoAtPath; + } + + IObservable ShowBrowseForDirectoryDialog() + { + return Observable.Start(() => + { + // We store this in a local variable to prevent it changing underneath us while the + // folder dialog is open. + var localBaseRepositoryPath = BaseRepositoryPath; + var browseResult = operatingSystem.Dialog.BrowseForDirectory(localBaseRepositoryPath, Resources.BrowseForDirectory); + + if (!browseResult.Success) + return; + + var directory = browseResult.DirectoryPath ?? localBaseRepositoryPath; + + try + { + BaseRepositoryPath = directory; + } + catch (Exception e) + { + // TODO: We really should limit this to exceptions we know how to handle. + log.Error(string.Format(CultureInfo.InvariantCulture, + "Failed to set base repository path.{0}localBaseRepositoryPath = \"{1}\"{0}BaseRepositoryPath = \"{2}\"{0}Chosen directory = \"{3}\"", + System.Environment.NewLine, localBaseRepositoryPath ?? "(null)", BaseRepositoryPath ?? "(null)", directory ?? "(null)"), e); + } + }, RxApp.MainThreadScheduler); + } + + /// + /// Path to clone repositories into + /// + public string BaseRepositoryPath + { + [return: AllowNull] + get { return baseRepositoryPath; } + set { this.RaiseAndSetIfChanged(ref baseRepositoryPath, value); } + } + + /// + /// Fires off the cloning process + /// + public IReactiveCommand CloneCommand { get; private set; } + + ISimpleRepositoryModel selectedRepository; + /// + /// Selected repository to clone + /// + [AllowNull] + public ISimpleRepositoryModel SelectedRepository + { + [return: AllowNull] + get { return selectedRepository; } + set { this.RaiseAndSetIfChanged(ref selectedRepository, value); } + } + + public ICommand BrowseForDirectory + { + get { return browseForDirectoryCommand; } + } + + public bool CanClone + { + get { return canClone.Value; } + } + + public ReactivePropertyValidator BaseRepositoryPathValidator + { + get; + private set; + } + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/IBaseCloneViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/IBaseCloneViewModel.cs new file mode 100644 index 0000000000..d5a4fd0264 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/IBaseCloneViewModel.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Reactive; +using GitHub.Models; +using ReactiveUI; +using System.Collections.ObjectModel; + +namespace GitHub.ViewModels +{ + /// + /// ViewModel for the the Clone Repository dialog + /// + public interface IBaseCloneViewModel : IViewModel, IRepositoryCreationTarget + { + /// + /// Command to clone the currently selected repository. + /// + IReactiveCommand CloneCommand { get; } + + ISimpleRepositoryModel SelectedRepository { get; set; } + } +} diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/StartPageCloneView.xaml b/src/GitHub.VisualStudio/UI/Views/Controls/StartPageCloneView.xaml new file mode 100644 index 0000000000..244659815a --- /dev/null +++ b/src/GitHub.VisualStudio/UI/Views/Controls/StartPageCloneView.xaml @@ -0,0 +1,329 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +