| File: PackageExtraction\PackagePathHelper.cs | Web Access |
| Project: 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.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using NuGet.Common; using NuGet.Packaging.Core; using NuGet.Versioning; namespace NuGet.Packaging { // HACK: TODO: This entire class is a hack. This is in place only for back-compat reasons // If the version was always normalized for package directory and package file name, there would be no issue :( public static class PackagePathHelper { internal static IEnumerable<string> GetFiles(string root, string path, string filter, bool recursive) { path = PathUtility.EnsureTrailingSlash(Path.Combine(root, path)); if (string.IsNullOrEmpty(filter)) { filter = "*.*"; } try { if (!Directory.Exists(path)) { return Enumerable.Empty<string>(); } return Directory.EnumerateFiles(path, filter, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly); } catch (UnauthorizedAccessException) { } catch (DirectoryNotFoundException) { } return Enumerable.Empty<string>(); } internal static IEnumerable<string> GetDirectories(string root, string path) { try { path = PathUtility.EnsureTrailingSlash(Path.Combine(root, path)); if (!Directory.Exists(path)) { return Enumerable.Empty<string>(); } return Directory.EnumerateDirectories(path); } catch (UnauthorizedAccessException) { } catch (DirectoryNotFoundException) { } return Enumerable.Empty<string>(); } private static IEnumerable<string> GetPackageFiles(string root, string filter) { filter = filter ?? "*" + PackagingCoreConstants.NupkgExtension; Debug.Assert( filter.EndsWith(PackagingCoreConstants.NupkgExtension, StringComparison.OrdinalIgnoreCase) || filter.EndsWith(PackagingCoreConstants.NuspecExtension, StringComparison.OrdinalIgnoreCase)); // Check for package files one level deep. We use this at package install time // to determine the set of installed packages. Installed packages are copied to // {id}.{version}\{packagefile}.{extension}. foreach (var dir in GetDirectories(root, string.Empty)) { foreach (var path in GetFiles(root, dir, filter, recursive: false)) { yield return path; } } // Check top level directory foreach (var path in GetFiles(root, string.Empty, filter, recursive: false)) { yield return path; } } private static bool FileNameMatchesPattern(PackageIdentity packageIdentity, string path) { var packageId = packageIdentity.Id; var name = Path.GetFileNameWithoutExtension(path); NuGetVersion? parsedVersion; // When matching by pattern, we will always have a version token. Packages without versions would be matched early on by the version-less path resolver // when doing an exact match. return name.Length > packageId.Length && NuGetVersion.TryParse(name.Substring(packageId.Length + 1), out parsedVersion) && parsedVersion.Equals(packageIdentity.Version); } public static IEnumerable<string> GetPackageLookupPaths(PackageIdentity packageIdentity, PackagePathResolver packagePathResolver) { if (packageIdentity == null) { throw new ArgumentNullException(nameof(packageIdentity)); } if (packageIdentity.Version == null) { throw new ArgumentException( string.Format(CultureInfo.CurrentCulture, Strings.PropertyCannotBeNull, nameof(packageIdentity.Version)), nameof(packageIdentity)); } if (packagePathResolver == null) { throw new ArgumentNullException(nameof(packagePathResolver)); } var packageId = packageIdentity.Id.ToLowerInvariant(); var version = packageIdentity.Version; var root = packagePathResolver.Root; // Files created by the path resolver. This would take into account the non-side-by-side scenario // and we do not need to match this for id and version. var packageFileName = packagePathResolver.GetPackageFileName(packageIdentity); var manifestFileName = Path.ChangeExtension(packageFileName, PackagingCoreConstants.NuspecExtension); var filesMatchingFullName = Enumerable.Concat( GetPackageFiles(root, packageFileName), GetPackageFiles(root, manifestFileName)); if (version != null && version.Version.Revision < 1) { // If the build or revision number is not set, we need to look for combinations of the format // * Foo.1.2.nupkg // * Foo.1.2.3.nupkg // * Foo.1.2.0.nupkg // * Foo.1.2.0.0.nupkg // To achieve this, we would look for files named 1.2*.nupkg if both build and revision are 0 and // 1.2.3*.nupkg if only the revision is set to 0. var partialName = version.Version.Build < 1 ? string.Join(".", packageId, version.Version.Major, version.Version.Minor) : string.Join(".", packageId, version.Version.Major, version.Version.Minor, version.Version.Build); var partialManifestName = partialName + "*" + PackagingCoreConstants.NuspecExtension; partialName += "*" + PackagingCoreConstants.NupkgExtension; // Partial names would result is gathering package with matching major and minor but different build and revision. // Attempt to match the version in the path to the version we're interested in. var partialNameMatches = GetPackageFiles(root, partialName).Where(path => FileNameMatchesPattern(packageIdentity, path)); var partialManifestNameMatches = GetPackageFiles(root, partialManifestName).Where( path => FileNameMatchesPattern(packageIdentity, path)); return Enumerable.Concat(filesMatchingFullName, partialNameMatches).Concat(partialManifestNameMatches); } return filesMatchingFullName; } public static string? GetInstalledPackageFilePath(PackageIdentity packageIdentity, PackagePathResolver packagePathResolver) { var packageLookupPaths = GetPackageLookupPaths(packageIdentity, packagePathResolver); // TODO: Not handling nuspec-only scenarios foreach (var packageLookupPath in packageLookupPaths) { if (packageLookupPath.EndsWith(PackagingCoreConstants.NupkgExtension, StringComparison.OrdinalIgnoreCase) && File.Exists(packageLookupPath)) { // This is an installed package lookup path which matches the packageIdentity for the given packagePathResolver return packageLookupPath; } } return null; } } }