PackageInstallerServiceFactory.cs 28.9 KB
Newer Older
S
Sam Harwell 已提交
1
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.
2 3 4 5 6 7

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
8
using System.IO;
9 10 11 12
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
13
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
14
using Microsoft.CodeAnalysis.ErrorReporting;
15
using Microsoft.CodeAnalysis.Host.Mef;
16
using Microsoft.CodeAnalysis.Notification;
17 18
using Microsoft.CodeAnalysis.Packaging;
using Microsoft.CodeAnalysis.Shared.Utilities;
19
using Microsoft.CodeAnalysis.SymbolSearch;
20 21 22
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
23
using Microsoft.VisualStudio.LanguageServices.SymbolSearch;
24
using Microsoft.VisualStudio.LanguageServices.Utilities;
25 26 27
using Microsoft.VisualStudio.Shell.Interop;
using NuGet.VisualStudio;
using Roslyn.Utilities;
28
using SVsServiceProvider = Microsoft.VisualStudio.Shell.SVsServiceProvider;
29 30 31

namespace Microsoft.VisualStudio.LanguageServices.Packaging
{
32 33
    using Workspace = Microsoft.CodeAnalysis.Workspace;

34 35 36
    /// <summary>
    /// Free threaded wrapper around the NuGet.VisualStudio STA package installer interfaces.
    /// We want to be able to make queries about packages from any thread.  For example, the
37
    /// add-NuGet-reference feature wants to know what packages a project already has 
38 39 40 41 42 43
    /// references to.  NuGet.VisualStudio provides this information, but only in a COM STA 
    /// manner.  As we don't want our background work to bounce and block on the UI thread 
    /// we have this helper class which queries the information on the UI thread and caches
    /// the data so it can be read from the background.
    /// </summary>
    [ExportWorkspaceService(typeof(IPackageInstallerService)), Shared]
44
    internal partial class PackageInstallerService : AbstractDelayStartedService, IPackageInstallerService, IVsSearchProviderCallback
45 46
    {
        private readonly object _gate = new object();
47
        private readonly VisualStudioWorkspaceImpl _workspace;
48
        private readonly SVsServiceProvider _serviceProvider;
49 50
        private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService;

C
CyrusNajmabadi 已提交
51 52 53
        // We refer to the package services through proxy types so that we can
        // delay loading their DLLs until we actually need them.
        private IPackageServicesProxy _packageServices;
54 55 56 57 58 59 60 61 62

        private CancellationTokenSource _tokenSource = new CancellationTokenSource();

        // We keep track of what types of changes we've seen so we can then determine what to
        // refresh on the UI thread.  If we hear about project changes, we only refresh that
        // project.  If we hear about a solution level change, we'll refresh all projects.
        private bool _solutionChanged;
        private HashSet<ProjectId> _changedProjects = new HashSet<ProjectId>();

63 64
        private readonly ConcurrentDictionary<ProjectId, ProjectState> _projectToInstalledPackageAndVersion =
            new ConcurrentDictionary<ProjectId, ProjectState>();
65 66

        [ImportingConstructor]
67
        [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
68
        public PackageInstallerService(
69
            IThreadingContext threadingContext,
70
            VisualStudioWorkspaceImpl workspace,
71
            SVsServiceProvider serviceProvider,
72
            IVsEditorAdaptersFactoryService editorAdaptersFactoryService)
73
            : base(threadingContext, workspace, SymbolSearchOptions.Enabled,
74 75
                              SymbolSearchOptions.SuggestForTypesInReferenceAssemblies,
                              SymbolSearchOptions.SuggestForTypesInNuGetPackages)
76
        {
77
            _workspace = workspace;
78
            _serviceProvider = serviceProvider;
79 80 81
            _editorAdaptersFactoryService = editorAdaptersFactoryService;
        }

82
        public ImmutableArray<PackageSource> PackageSources { get; private set; } = ImmutableArray<PackageSource>.Empty;
83

84 85
        public event EventHandler PackageSourcesChanged;

C
CyrusNajmabadi 已提交
86
        private bool IsEnabled => _packageServices != null;
87

C
CyrusNajmabadi 已提交
88
        bool IPackageInstallerService.IsEnabled(ProjectId projectId)
89
        {
C
CyrusNajmabadi 已提交
90
            if (_packageServices == null)
91 92 93 94 95 96 97 98 99 100 101 102 103
            {
                return false;
            }

            if (_projectToInstalledPackageAndVersion.TryGetValue(projectId, out var state))
            {
                return state.IsEnabled;
            }

            // If we haven't scanned the project yet, assume that we're available for it.
            return true;
        }

104
        protected override void EnableService()
105
        {
106
            // Our service has been enabled.  Now load the VS package dlls.
107
            var componentModel = (IComponentModel)_serviceProvider.GetService(typeof(SComponentModel));
108 109

            var packageInstallerServices = componentModel.GetExtensions<IVsPackageInstallerServices>().FirstOrDefault();
110
            var packageInstaller = componentModel.GetExtensions<IVsPackageInstaller2>().FirstOrDefault();
111 112
            var packageUninstaller = componentModel.GetExtensions<IVsPackageUninstaller>().FirstOrDefault();
            var packageSourceProvider = componentModel.GetExtensions<IVsPackageSourceProvider>().FirstOrDefault();
113

114 115 116 117
            if (packageInstallerServices == null ||
                packageInstaller == null ||
                packageUninstaller == null ||
                packageSourceProvider == null)
118 119 120 121
            {
                return;
            }

C
CyrusNajmabadi 已提交
122 123
            _packageServices = new PackageServicesProxy(
                packageInstallerServices, packageInstaller, packageUninstaller, packageSourceProvider);
124 125 126

            // Start listening to additional events workspace changes.
            _workspace.WorkspaceChanged += OnWorkspaceChanged;
C
CyrusNajmabadi 已提交
127
            _packageServices.SourcesChanged += OnSourceProviderSourcesChanged;
C
CyrusNajmabadi 已提交
128 129
        }

130
        protected override void StartWorking()
131 132 133
        {
            this.AssertIsForeground();

134 135 136 137 138
            if (!this.IsEnabled)
            {
                return;
            }

139
            OnSourceProviderSourcesChanged(this, EventArgs.Empty);
140 141
            OnWorkspaceChanged(null, new WorkspaceChangeEventArgs(
                WorkspaceChangeKind.SolutionAdded, null, null));
142 143 144 145 146 147
        }

        private void OnSourceProviderSourcesChanged(object sender, EventArgs e)
        {
            if (!this.IsForeground())
            {
C
Cheryl Borley 已提交
148
                this.InvokeBelowInputPriorityAsync(() => OnSourceProviderSourcesChanged(sender, e));
149 150 151 152 153
                return;
            }

            this.AssertIsForeground();

154 155 156 157 158 159 160 161 162 163 164
            try
            {
                PackageSources = _packageServices.GetSources(includeUnOfficial: true, includeDisabled: false)
                    .Select(r => new PackageSource(r.Key, r.Value))
                    .ToImmutableArrayOrEmpty();
            }
            catch (Exception ex) when (ex is InvalidDataException || ex is InvalidOperationException)
            {
                // These exceptions can happen when the nuget.config file is broken.
                PackageSources = ImmutableArray<PackageSource>.Empty;
            }
165 166

            PackageSourcesChanged?.Invoke(this, EventArgs.Empty);
167 168 169
        }

        public bool TryInstallPackage(
170 171 172 173 174
            Workspace workspace,
            DocumentId documentId,
            string source,
            string packageName,
            string versionOpt,
175
            bool includePrerelease,
176
            CancellationToken cancellationToken)
177 178 179 180 181 182
        {
            this.AssertIsForeground();

            // The 'workspace == _workspace' line is probably not necessary. However, we include 
            // it just to make sure that someone isn't trying to install a package into a workspace
            // other than the VisualStudioWorkspace.
C
CyrusNajmabadi 已提交
183
            if (workspace == _workspace && _workspace != null && _packageServices != null)
184 185
            {
                var projectId = documentId.ProjectId;
186
                var dte = (EnvDTE.DTE)_serviceProvider.GetService(typeof(SDTE));
187 188 189 190 191
                var dteProject = _workspace.TryGetDTEProject(projectId);
                if (dteProject != null)
                {
                    var description = string.Format(ServicesVSResources.Install_0, packageName);

192 193
                    var undoManager = _editorAdaptersFactoryService.TryGetUndoManager(
                        workspace, documentId, cancellationToken);
194

195 196
                    return TryInstallAndAddUndoAction(
                        source, packageName, versionOpt, includePrerelease, dte, dteProject, undoManager);
197 198 199 200 201 202 203
                }
            }

            return false;
        }

        private bool TryInstallPackage(
204 205 206
            string source,
            string packageName,
            string versionOpt,
207
            bool includePrerelease,
208 209
            EnvDTE.DTE dte,
            EnvDTE.Project dteProject)
210 211 212
        {
            try
            {
C
CyrusNajmabadi 已提交
213
                if (!_packageServices.IsPackageInstalled(dteProject, packageName))
214 215
                {
                    dte.StatusBar.Text = string.Format(ServicesVSResources.Installing_0, packageName);
216 217 218 219 220 221 222 223 224 225 226

                    if (versionOpt == null)
                    {
                        _packageServices.InstallLatestPackage(
                            source, dteProject, packageName, includePrerelease, ignoreDependencies: false);
                    }
                    else
                    {
                        _packageServices.InstallPackage(
                            source, dteProject, packageName, versionOpt, ignoreDependencies: false);
                    }
227 228 229 230 231 232 233 234 235 236

                    var installedVersion = GetInstalledVersion(packageName, dteProject);
                    dte.StatusBar.Text = string.Format(ServicesVSResources.Installing_0_completed,
                        GetStatusBarText(packageName, installedVersion));

                    return true;
                }

                // fall through.
            }
237
            catch (Exception e) when (FatalError.ReportWithoutCrash(e))
238
            {
239
                dte.StatusBar.Text = string.Format(ServicesVSResources.Package_install_failed_colon_0, e.Message);
240 241 242

                var notificationService = _workspace.Services.GetService<INotificationService>();
                notificationService?.SendNotification(
243
                    string.Format(ServicesVSResources.Installing_0_failed_Additional_information_colon_1, packageName, e.Message),
244 245
                    severity: NotificationSeverity.Error);

246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
                // fall through.
            }

            return false;
        }

        private static string GetStatusBarText(string packageName, string installedVersion)
        {
            return installedVersion == null ? packageName : $"{packageName} - {installedVersion}";
        }

        private bool TryUninstallPackage(
            string packageName, EnvDTE.DTE dte, EnvDTE.Project dteProject)
        {
            this.AssertIsForeground();

            try
            {
C
CyrusNajmabadi 已提交
264
                if (_packageServices.IsPackageInstalled(dteProject, packageName))
265 266 267
                {
                    dte.StatusBar.Text = string.Format(ServicesVSResources.Uninstalling_0, packageName);
                    var installedVersion = GetInstalledVersion(packageName, dteProject);
C
CyrusNajmabadi 已提交
268
                    _packageServices.UninstallPackage(dteProject, packageName, removeDependencies: true);
269

C
CyrusNajmabadi 已提交
270
                    dte.StatusBar.Text = string.Format(ServicesVSResources.Uninstalling_0_completed,
271 272 273 274 275 276 277
                        GetStatusBarText(packageName, installedVersion));

                    return true;
                }

                // fall through.
            }
278
            catch (Exception e) when (FatalError.ReportWithoutCrash(e))
279
            {
280
                dte.StatusBar.Text = string.Format(ServicesVSResources.Package_uninstall_failed_colon_0, e.Message);
281 282 283

                var notificationService = _workspace.Services.GetService<INotificationService>();
                notificationService?.SendNotification(
284
                    string.Format(ServicesVSResources.Uninstalling_0_failed_Additional_information_colon_1, packageName, e.Message),
285 286
                    severity: NotificationSeverity.Error);

287 288 289 290 291 292 293 294 295 296 297 298
                // fall through.
            }

            return false;
        }

        private string GetInstalledVersion(string packageName, EnvDTE.Project dteProject)
        {
            this.AssertIsForeground();

            try
            {
C
CyrusNajmabadi 已提交
299
                var installedPackages = _packageServices.GetInstalledPackages(dteProject);
300 301 302
                var metadata = installedPackages.FirstOrDefault(m => m.Id == packageName);
                return metadata?.VersionString;
            }
303 304 305 306 307
            catch (ArgumentException e) when (IsKnownNugetIssue(e))
            {
                // Nuget may throw an ArgumentException when there is something about the project 
                // they do not like/support.
            }
308
            catch (Exception e) when (FatalError.ReportWithoutCrash(e))
309 310
            {
            }
311 312 313 314 315 316 317 318 319 320 321

            return null;
        }

        private bool IsKnownNugetIssue(ArgumentException exception)
        {
            // See https://github.com/NuGet/Home/issues/4706
            // Nuget throws on legal projects.  We do not want to report this exception
            // as it is known (and NFWs are expensive), but we do want to report if we 
            // run into anything else.
            return exception.Message.Contains("is not a valid version string");
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
        }

        private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e)
        {
            ThisCanBeCalledOnAnyThread();

            bool localSolutionChanged = false;
            ProjectId localChangedProject = null;
            switch (e.Kind)
            {
                default:
                    // Nothing to do for any other events.
                    return;

                case WorkspaceChangeKind.ProjectAdded:
                case WorkspaceChangeKind.ProjectChanged:
                case WorkspaceChangeKind.ProjectReloaded:
                case WorkspaceChangeKind.ProjectRemoved:
                    localChangedProject = e.ProjectId;
                    break;

                case WorkspaceChangeKind.SolutionAdded:
                case WorkspaceChangeKind.SolutionChanged:
                case WorkspaceChangeKind.SolutionCleared:
                case WorkspaceChangeKind.SolutionReloaded:
                case WorkspaceChangeKind.SolutionRemoved:
                    localSolutionChanged = true;
                    break;
            }

            lock (_gate)
            {
                // Augment the data that the foreground thread will process.
                _solutionChanged |= localSolutionChanged;
                if (localChangedProject != null)
                {
                    _changedProjects.Add(localChangedProject);
                }

                // Now cancel any inflight work that is processing the data.
                _tokenSource.Cancel();
                _tokenSource = new CancellationTokenSource();

                // And enqueue a new job to process things.  Wait one second before starting.
                // That way if we get a flurry of events we'll end up processing them after
                // they've all come in.
                var cancellationToken = _tokenSource.Token;
                Task.Delay(TimeSpan.FromSeconds(1), cancellationToken)
370 371 372 373
                    .ContinueWith(
                        async _ =>
                        {
                            await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken);
374 375
                            cancellationToken.ThrowIfCancellationRequested();

376 377 378 379 380
                            ProcessBatchedChangesOnForeground(cancellationToken);
                        },
                        cancellationToken,
                        TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously,
                        TaskScheduler.Default).Unwrap();
381 382 383 384 385 386 387 388 389 390 391 392 393 394
            }
        }

        private void ProcessBatchedChangesOnForeground(CancellationToken cancellationToken)
        {
            this.AssertIsForeground();

            // If we've been asked to stop, then there's no point proceeding.
            if (cancellationToken.IsCancellationRequested)
            {
                return;
            }

            // If we've been disconnected, then there's no point proceeding.
C
CyrusNajmabadi 已提交
395
            if (_workspace == null || _packageServices == null)
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
            {
                return;
            }

            // Get a project to process.
            var solution = _workspace.CurrentSolution;
            var projectId = DequeueNextProject(solution);
            if (projectId == null)
            {
                // No project to process, nothing to do.
                return;
            }

            // Process this single project.
            ProcessProjectChange(solution, projectId);

            // After processing this single project, yield so the foreground thread
            // can do more work.  Then go and loop again so we can process the 
            // rest of the projects.
415 416 417 418
            Task.Factory.SafeStartNewFromAsync(
                async () =>
                {
                    await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
419 420
                    cancellationToken.ThrowIfCancellationRequested();

421 422 423 424
                    ProcessBatchedChangesOnForeground(cancellationToken);
                },
                cancellationToken,
                TaskScheduler.Default);
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453
        }

        private ProjectId DequeueNextProject(Solution solution)
        {
            this.AssertIsForeground();

            lock (_gate)
            {
                // If we detected a solution change, then we need to process all projects.
                // This includes all the projects that we already know about, as well as
                // all the projects in the current workspace solution.
                if (_solutionChanged)
                {
                    _changedProjects.AddRange(solution.ProjectIds);
                    _changedProjects.AddRange(_projectToInstalledPackageAndVersion.Keys);
                }

                _solutionChanged = false;

                // Remove and return the first project in the list.
                var projectId = _changedProjects.FirstOrDefault();
                _changedProjects.Remove(projectId);
                return projectId;
            }
        }

        private void ProcessProjectChange(Solution solution, ProjectId projectId)
        {
            this.AssertIsForeground();
454

455
            // Remove anything we have associated with this project.
456
            _projectToInstalledPackageAndVersion.TryRemove(projectId, out var projectState);
457

458 459
            var project = solution.GetProject(projectId);
            if (project == null)
460 461 462 463 464
            {
                // Project was removed.  Nothing needs to be done.
                return;
            }

465 466 467 468 469 470 471 472 473 474
            // We really only need to know the NuGet status for managed language projects.
            // Also, the NuGet APIs may throw on some projects that don't implement the 
            // full set of DTE APIs they expect.  So we filter down to just C# and VB here
            // as we know these languages are safe to build up this index for.
            if (project.Language != LanguageNames.CSharp &&
                project.Language != LanguageNames.VisualBasic)
            {
                return;
            }

475 476 477 478
            // Project was changed in some way.  Let's go find the set of installed packages for it.
            var dteProject = _workspace.TryGetDTEProject(projectId);
            if (dteProject == null)
            {
479
                // Don't have a DTE project for this project ID.  not something we can query NuGet for.
480 481 482
                return;
            }

483
            var installedPackages = new MultiDictionary<string, string>();
484
            var isEnabled = false;
485

486
            // Calling into NuGet.  Assume they may fail for any reason.
487 488
            try
            {
C
CyrusNajmabadi 已提交
489
                var installedPackageMetadata = _packageServices.GetInstalledPackages(dteProject);
490 491
                foreach (var metadata in installedPackageMetadata)
                {
C
CyrusNajmabadi 已提交
492 493 494 495
                    if (metadata.VersionString != null)
                    {
                        installedPackages.Add(metadata.Id, metadata.VersionString);
                    }
496
                }
C
CyrusNajmabadi 已提交
497

498 499
                isEnabled = true;
            }
500
            catch (ArgumentException e) when (IsKnownNugetIssue(e))
501 502 503
            {
                // Nuget may throw an ArgumentException when there is something about the project 
                // they do not like/support.
504
            }
505
            catch (Exception e) when (FatalError.ReportWithoutCrash(e))
506 507 508
            {
            }

509 510 511
            var state = new ProjectState(isEnabled, installedPackages);
            _projectToInstalledPackageAndVersion.AddOrUpdate(
                projectId, state, (_1, _2) => state);
512 513 514 515 516
        }

        public bool IsInstalled(Workspace workspace, ProjectId projectId, string packageName)
        {
            ThisCanBeCalledOnAnyThread();
C
CyrusNajmabadi 已提交
517
            return _projectToInstalledPackageAndVersion.TryGetValue(projectId, out var installedPackages) &&
518
                installedPackages.InstalledPackageToVersion.ContainsKey(packageName);
519 520
        }

521
        public ImmutableArray<string> GetInstalledVersions(string packageName)
522 523 524 525
        {
            ThisCanBeCalledOnAnyThread();

            var installedVersions = new HashSet<string>();
526
            foreach (var state in _projectToInstalledPackageAndVersion.Values)
527
            {
528
                installedVersions.AddRange(state.InstalledPackageToVersion[packageName]);
529 530 531 532 533 534 535 536 537 538 539 540 541
            }

            // Order the versions with a weak heuristic so that 'newer' versions come first.
            // Essentially, we try to break the version on dots, and then we use a LogicalComparer
            // to try to more naturally order the things we see between the dots.
            var versionsAndSplits = installedVersions.Select(v => new { Version = v, Split = v.Split('.') }).ToList();

            versionsAndSplits.Sort((v1, v2) =>
            {
                var diff = CompareSplit(v1.Split, v2.Split);
                return diff != 0 ? diff : -v1.Version.CompareTo(v2.Version);
            });

542
            return versionsAndSplits.Select(v => v.Version).ToImmutableArray();
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572
        }

        private int CompareSplit(string[] split1, string[] split2)
        {
            ThisCanBeCalledOnAnyThread();

            for (int i = 0, n = Math.Min(split1.Length, split2.Length); i < n; i++)
            {
                // Prefer things that look larger.  i.e. 7 should come before 6. 
                // Use a logical string comparer so that 10 is understood to be
                // greater than 3.
                var diff = -LogicalStringComparer.Instance.Compare(split1[i], split2[i]);
                if (diff != 0)
                {
                    return diff;
                }
            }

            // Choose the one with more parts.
            return split2.Length - split1.Length;
        }

        public IEnumerable<Project> GetProjectsWithInstalledPackage(Solution solution, string packageName, string version)
        {
            ThisCanBeCalledOnAnyThread();

            var result = new List<Project>();

            foreach (var kvp in this._projectToInstalledPackageAndVersion)
            {
573
                var state = kvp.Value;
574
                var versionSet = state.InstalledPackageToVersion[packageName];
575
                if (versionSet.Contains(version))
576
                {
577 578
                    var project = solution.GetProject(kvp.Key);
                    if (project != null)
579
                    {
580
                        result.Add(project);
581 582 583 584 585 586 587 588 589 590 591
                    }
                }
            }

            return result;
        }

        public void ShowManagePackagesDialog(string packageName)
        {
            this.AssertIsForeground();

592
            var shell = (IVsShell)_serviceProvider.GetService(typeof(SVsShell));
593 594 595 596 597 598
            if (shell == null)
            {
                return;
            }

            var nugetGuid = new Guid("5fcc8577-4feb-4d04-ad72-d6c629b083cc");
C
CyrusNajmabadi 已提交
599
            shell.LoadPackage(ref nugetGuid, out var nugetPackage);
600 601 602 603 604 605
            if (nugetPackage == null)
            {
                return;
            }

            // We're able to launch the package manager (with an item in its search box) by
606
            // using the IVsSearchProvider API that the NuGet package exposes.
607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651
            //
            // We get that interface for it and then pass it a SearchQuery that effectively
            // wraps the package name we're looking for.  The NuGet package will then read
            // out that string and populate their search box with it.
            var extensionProvider = (IVsPackageExtensionProvider)nugetPackage;
            var extensionGuid = new Guid("042C2B4B-C7F7-49DB-B7A2-402EB8DC7892");
            var emptyGuid = Guid.Empty;
            var searchProvider = (IVsSearchProvider)extensionProvider.CreateExtensionInstance(ref emptyGuid, ref extensionGuid);
            var task = searchProvider.CreateSearch(dwCookie: 1, pSearchQuery: new SearchQuery(packageName), pSearchCallback: this);
            task.Start();
        }

        public void ReportProgress(IVsSearchTask pTask, uint dwProgress, uint dwMaxProgress)
        {
        }

        public void ReportComplete(IVsSearchTask pTask, uint dwResultsFound)
        {
        }

        public void ReportResult(IVsSearchTask pTask, IVsSearchItemResult pSearchItemResult)
        {
            pSearchItemResult.InvokeAction();
        }

        public void ReportResults(IVsSearchTask pTask, uint dwResults, IVsSearchItemResult[] pSearchItemResults)
        {
        }

        private class SearchQuery : IVsSearchQuery
        {
            public SearchQuery(string packageName)
            {
                this.SearchString = packageName;
            }

            public string SearchString { get; }

            public uint ParseError => 0;

            public uint GetTokens(uint dwMaxTokens, IVsSearchToken[] rgpSearchTokens)
            {
                return 0;
            }
        }
652

C
CyrusNajmabadi 已提交
653
        private class PackageServicesProxy : IPackageServicesProxy
654
        {
655
            private readonly IVsPackageInstaller2 _packageInstaller;
C
CyrusNajmabadi 已提交
656 657 658
            private readonly IVsPackageInstallerServices _packageInstallerServices;
            private readonly IVsPackageSourceProvider _packageSourceProvider;
            private readonly IVsPackageUninstaller _packageUninstaller;
659

660 661 662 663 664
            public PackageServicesProxy(
                IVsPackageInstallerServices packageInstallerServices,
                IVsPackageInstaller2 packageInstaller,
                IVsPackageUninstaller packageUninstaller,
                IVsPackageSourceProvider packageSourceProvider)
665
            {
C
CyrusNajmabadi 已提交
666 667 668 669
                _packageInstallerServices = packageInstallerServices;
                _packageInstaller = packageInstaller;
                _packageUninstaller = packageUninstaller;
                _packageSourceProvider = packageSourceProvider;
670 671 672 673 674 675
            }

            public event EventHandler SourcesChanged
            {
                add
                {
C
CyrusNajmabadi 已提交
676
                    _packageSourceProvider.SourcesChanged += value;
677 678 679 680
                }

                remove
                {
C
CyrusNajmabadi 已提交
681
                    _packageSourceProvider.SourcesChanged -= value;
682 683 684
                }
            }

685
            public IEnumerable<PackageMetadata> GetInstalledPackages(EnvDTE.Project project)
686
            {
687 688 689
                return _packageInstallerServices.GetInstalledPackages(project)
                                  .Select(m => new PackageMetadata(m.Id, m.VersionString))
                                  .ToList();
690 691
            }

692 693 694 695 696 697 698 699 700 701 702 703
            public bool IsPackageInstalled(EnvDTE.Project project, string id)
                => _packageInstallerServices.IsPackageInstalled(project, id);

            public void InstallPackage(string source, EnvDTE.Project project, string packageId, string version, bool ignoreDependencies)
                => _packageInstaller.InstallPackage(source, project, packageId, version, ignoreDependencies);

            public void InstallLatestPackage(string source, EnvDTE.Project project, string packageId, bool includePrerelease, bool ignoreDependencies)
                => _packageInstaller.InstallLatestPackage(source, project, packageId, includePrerelease, ignoreDependencies);

            public IEnumerable<KeyValuePair<string, string>> GetSources(bool includeUnOfficial, bool includeDisabled)
                => _packageSourceProvider.GetSources(includeUnOfficial, includeDisabled);

704
            public void UninstallPackage(EnvDTE.Project project, string packageId, bool removeDependencies)
705
                => _packageUninstaller.UninstallPackage(project, packageId, removeDependencies);
706
        }
707
    }
S
Sam Harwell 已提交
708
}