File: Utility\AddPackageCommandUtility.cs
Web Access
Project: NuGet.CommandLine.XPlat.csproj (NuGet.CommandLine.XPlat)
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

#nullable disable

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NuGet.Common;
using NuGet.Configuration;
using NuGet.Commands;
using NuGet.Protocol;
using NuGet.Protocol.Core.Types;
using NuGet.Versioning;

namespace NuGet.CommandLine.XPlat.Utility
{
    internal static class AddPackageCommandUtility
    {
        /// <summary>
        /// Return the latest version available in the sources
        /// </summary>
        /// <param name="sources">Sources to look at</param>
        /// <param name="logger">Logger</param>
        /// <param name="packageId">Package to look for</param>
        /// <param name="prerelease">Whether to include prerelease versions</param>
        /// <param name="ignoreMinPublishAge">Whether to ignore the minimum publish age configured on package sources</param>
        /// <param name="cancellationToken"></param>
        /// <returns>Return the latest version available from multiple sources and if no version is found returns null.</returns>

        public static async Task<NuGetVersion> GetLatestVersionFromSourcesAsync(
            IList<PackageSource> sources,
            ILogger logger,
            string packageId,
            bool prerelease,
            bool ignoreMinPublishAge,
            CancellationToken cancellationToken)
        {
            var maxTasks = Environment.ProcessorCount;
            var tasks = new List<Task<NuGetVersion>>();
            var latestReleaseList = new List<NuGetVersion>();

            foreach (var source in sources)
            {
                tasks.Add(Task.Run(() => GetLatestVersionFromSourceAsync(source, logger, packageId, prerelease, ignoreMinPublishAge, cancellationToken)));
                if (maxTasks <= tasks.Count)
                {
                    var finishedTask = await Task.WhenAny(tasks);
                    tasks.Remove(finishedTask);
                    latestReleaseList.Add(await finishedTask);
                }
            }

            await Task.WhenAll(tasks);

            foreach (var t in tasks)
            {
                var result = await t;
                if (result != null)
                {
                    latestReleaseList.Add(result);
                }
            }

            return latestReleaseList.Max();
        }

        /// <summary>
        /// Return the latest version of the source
        /// </summary>
        /// <param name="source">Source to look at</param>
        /// <param name="logger">Logger</param>
        /// <param name="packageId">Package to look for</param>
        /// <param name="prerelease">Whether to include prerelease versions</param>
        /// <param name="ignoreMinPublishAge">Whether to ignore the minimum publish age configured on the package source</param>
        /// <param name="cancellationToken"></param>
        /// <returns>Returns the latest version available from a source or a null if non is found.</returns>
        public static async Task<NuGetVersion> GetLatestVersionFromSourceAsync(
            PackageSource source,
            ILogger logger,
            string packageId,
            bool prerelease,
            bool ignoreMinPublishAge,
            CancellationToken cancellationToken)
        {
            SourceRepository repository = Repository.Factory.GetCoreV3(source);
            PackageMetadataResource resource = await repository.GetResourceAsync<PackageMetadataResource>(cancellationToken);

            using (var cache = new SourceCacheContext())
            {
                IEnumerable<IPackageSearchMetadata> packages = await resource.GetMetadataAsync(
                    packageId,
                    includePrerelease: prerelease,
                    includeUnlisted: false,
                    cache,
                    logger,
                    cancellationToken
                );

                if (!ignoreMinPublishAge && source.MinPublishAge > TimeSpan.Zero && packages != null)
                {
                    var packagesWithPublishDates = packages.ToList();
                    IPackageSearchMetadata packageWithoutPublishDate = packagesWithPublishDates.FirstOrDefault(package => package.Published == null);
                    if (packageWithoutPublishDate != null)
                    {
                        throw new CommandException(Messages.Error_PackagePublishDateMissing(
                            source.Name,
                            packageId,
                            packageWithoutPublishDate.Identity.Version.ToNormalizedString()));
                    }

                    DateTimeOffset publishCutoff = DateTimeOffset.UtcNow - source.MinPublishAge;
                    packages = packagesWithPublishDates.Where(package => package.Published <= publishCutoff);
                }

                return packages?.Max(package => package.Identity.Version);
            }
        }

        /// <summary>
        /// Returns the PackageSource with its credentials if available
        /// </summary>
        /// <param name="requestedSources">Sources to match</param>
        /// <param name="configFilePaths">Config to use for credentials</param>
        /// <returns>Return a list of package sources</returns>
        public static List<PackageSource> EvaluateSources(IList<PackageSource> requestedSources, IList<string> configFilePaths)
        {
            using (var settingsLoadingContext = new SettingsLoadingContext())
            {
                ISettings settings = Settings.LoadImmutableSettingsGivenConfigPaths(configFilePaths, settingsLoadingContext);
                return EvaluateSources(requestedSources, settings);
            }
        }

        /// <summary>
        /// Returns package sources with their configured settings, including credentials and minimum publish age.
        /// </summary>
        /// <param name="requestedSources">Sources to match</param>
        /// <param name="settings">Settings containing the source configuration</param>
        /// <returns>Return a list of configured package sources</returns>
        public static List<PackageSource> EvaluateSources(IList<PackageSource> requestedSources, ISettings settings)
        {
            var packageSources = new List<PackageSource>();

            var packageSourceProvider = new PackageSourceProvider(settings);
            IEnumerable<PackageSource> packageProviderSources = packageSourceProvider.LoadPackageSources();

            for (int i = 0; i < requestedSources.Count; i++)
            {
                PackageSource matchedSource = packageProviderSources.FirstOrDefault(e => e.Source == requestedSources[i].Source);
                if (matchedSource == null)
                {
                    packageSources.Add(requestedSources[i]);
                }
                else
                {
                    packageSources.Add(matchedSource);
                }
            }

            return packageSources;
        }
    }
}