File: FallbackPackagePathResolver.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.Packaging\NuGet.Packaging.csproj (NuGet.Packaging)
// 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.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using NuGet.Common;
using NuGet.Packaging.Core;
using NuGet.Versioning;

namespace NuGet.Packaging
{
    public class FallbackPackagePathResolver
    {
        private readonly List<VersionFolderPathResolver> _pathResolvers;

        /// <summary>
        /// Creates a package folder path resolver that scans multiple folders to find a package.
        /// </summary>
        /// <param name="pathContext">NuGet paths loaded from NuGet.Config settings.</param>
        public FallbackPackagePathResolver(INuGetPathContext pathContext)
            : this(pathContext.UserPackageFolder, pathContext.FallbackPackageFolders)
        {

        }

        public FallbackPackagePathResolver(string userPackageFolder, IEnumerable<string> fallbackPackageFolders)
        {
            if (fallbackPackageFolders == null)
            {
                throw new ArgumentNullException(nameof(fallbackPackageFolders));
            }

            if (userPackageFolder == null)
            {
                throw new ArgumentNullException(nameof(userPackageFolder));
            }

            var packageFolders = new List<string>();

            // The user's packages folder may not exist, this is expected if the fallback
            // folders contain all packages.
            if (Directory.Exists(userPackageFolder))
            {
                packageFolders.Add(userPackageFolder);
            }

            // All fallback folders must exist
            foreach (var path in fallbackPackageFolders)
            {
                if (!Directory.Exists(path))
                {
                    var message = string.Format(
                        CultureInfo.CurrentCulture,
                        Strings.FallbackFolderNotFound,
                        path);

                    throw new PackagingException(message);
                }

                packageFolders.Add(path);
            }

            // Create path resolvers for each source.
            _pathResolvers = PathUtility.GetUniquePathsBasedOnOS(packageFolders)
                .Select(path => new VersionFolderPathResolver(path))
                .ToList();
        }

        /// <summary>
        /// Returns the root directory of an installed package.
        /// </summary>
        /// <param name="packageId">Package id.</param>
        /// <param name="version">Package version.</param>
        /// <returns>Returns the path if the package exists in any of the folders. Null if the package does not exist.</returns>
        public string? GetPackageDirectory(string packageId, string version)
        {
            return GetPackageDirectory(packageId, NuGetVersion.Parse(version));
        }

        /// <summary>
        /// Returns the root directory of an installed package.
        /// </summary>
        /// <param name="packageId">Package id.</param>
        /// <param name="version">Package version.</param>
        /// <returns>Returns the path if the package exists in any of the folders. Null if the package does not exist.</returns>
        public string? GetPackageDirectory(string packageId, NuGetVersion version)
        {
            // Find the package
            var info = GetPackageInfo(packageId, version);

            // Find the folder using the resolver for the folder where it was found.
            return info?.PathResolver.GetInstallPath(packageId, version);
        }

        /// <summary>
        /// Returns the package info along with a path resolver specific to the folder where the package exists.
        /// </summary>
        /// <param name="packageId">Package id.</param>
        /// <param name="version">Package version.</param>
        /// <returns>Returns the package info if the package exists in any of the folders. Null if the package does not exist.</returns>
        public FallbackPackagePathInfo? GetPackageInfo(string packageId, NuGetVersion version)
        {
            if (string.IsNullOrEmpty(packageId))
            {
                throw new ArgumentException(
                    string.Format(
                    CultureInfo.CurrentCulture,
                    Strings.StringCannotBeNullOrEmpty,
                    nameof(packageId)));
            }

            if (version == null)
            {
                throw new ArgumentNullException(nameof(version));
            }

            // Check each folder for the package.
            foreach (var resolver in _pathResolvers)
            {
                var hashPath = resolver.GetHashPath(packageId, version);

                if (File.Exists(hashPath))
                {
                    // If the hash exists we can use this path
                    return new FallbackPackagePathInfo(packageId, version, resolver);
                }

                // Perf: As hashPath is commonly found and the GetNupkgMetadataPath call is relatively
                // expensive, only request nupkgMetadataFilePath if hashPath isn't found
                var nupkgMetadataFilePath = resolver.GetNupkgMetadataPath(packageId, version);

                if (File.Exists(nupkgMetadataFilePath))
                {
                    // If the hash exists we can use this path
                    return new FallbackPackagePathInfo(packageId, version, resolver);
                }
            }

            // Not found
            return null;
        }
    }
}