Files
2026-09-21 12:15:57 +03:00

363 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using NugetForUnity.Configuration;
using NugetForUnity.Models;
using UnityEngine;
namespace NugetForUnity.PackageSource
{
/// <summary>
/// Represents a NuGet Package Source that uses packages stored on a local or network drive.
/// </summary>
[Serializable]
internal sealed class NugetPackageSourceLocal : INugetPackageSource
{
/// <summary>
/// Initializes a new instance of the <see cref="NugetPackageSourceLocal" /> class.
/// </summary>
/// <param name="name">The name of the package source.</param>
/// <param name="path">The path to the package source.</param>
public NugetPackageSourceLocal([NotNull] string name, [NotNull] string path)
{
Name = name;
SavedPath = path;
IsEnabled = true;
}
/// <inheritdoc />
[field: SerializeField]
public string Name { get; set; }
/// <inheritdoc />
[field: SerializeField]
public string SavedPath { get; set; }
/// <inheritdoc />
public string SavedProtocolVersion
{
get => null;
set
{
// local sources can't have protocol version
}
}
/// <inheritdoc />
[field: SerializeField]
public bool IsEnabled { get; set; }
/// <inheritdoc />
public string SavedUserName
{
get => null;
set
{
// local sources don't have credentials
}
}
/// <inheritdoc />
public string SavedPassword
{
get => null;
set
{
// local sources don't have credentials
}
}
/// <inheritdoc />
public bool SavedPasswordIsEncrypted
{
get => false;
set
{
// local sources don't have credentials
}
}
/// <inheritdoc />
public bool HasPassword
{
get => false;
set
{
// local sources don't have credentials
}
}
/// <inheritdoc />
public bool CredentialsStoredInExternalFile
{
get => false;
set
{
// local sources don't have credentials
}
}
/// <inheritdoc />
public bool EnableCredentialProvider
{
get => false;
set
{
// local sources can't have credentials
}
}
/// <summary>
/// Gets path, with the values of environment variables expanded.
/// </summary>
[NotNull]
private string ExpandedPath
{
get
{
var path = Environment.ExpandEnvironmentVariables(SavedPath);
if (!Path.IsPathRooted(path))
{
path = Path.Combine(ConfigurationManager.NugetConfigFileDirectoryPath, path);
}
return path;
}
}
/// <inheritdoc />
public List<INugetPackage> FindPackagesById(INugetPackageIdentifier package)
{
List<INugetPackage> foundPackages;
if (!package.HasVersionRange && !string.IsNullOrEmpty(package.Version))
{
var localPackagePath = GetNuPkgFilePath(package);
if (File.Exists(localPackagePath))
{
var localPackage = NugetPackageLocal.FromNupkgFile(localPackagePath, this);
foundPackages = new List<INugetPackage> { localPackage };
}
else
{
foundPackages = new List<INugetPackage>();
}
}
else
{
// TODO: Optimize to no longer use GetLocalPackages, since that loads the .nupkg itself
foundPackages = GetLocalPackages($"{package.Id}*", true, true);
}
// Return all the packages in the range of versions specified by 'package'.
foundPackages.RemoveAll(p => !package.InRange(p));
foundPackages.Sort();
return foundPackages;
}
/// <inheritdoc />
public INugetPackage GetSpecificPackage(INugetPackageIdentifier package)
{
// if multiple match we use the lowest version
return FindPackagesById(package).FirstOrDefault();
}
/// <inheritdoc />
public Task<List<INugetPackage>> SearchAsync(
string searchTerm = "",
bool includePrerelease = false,
int numberToGet = 15,
int numberToSkip = 0,
CancellationToken cancellationToken = default)
{
return Task.FromResult(GetLocalPackages($"*{searchTerm}*", false, includePrerelease, numberToSkip));
}
/// <inheritdoc />
public List<INugetPackage> GetUpdates(
IEnumerable<INugetPackage> packages,
bool includePrerelease = false,
string targetFrameworks = "",
string versionConstraints = "",
CancellationToken token = default)
{
return GetLocalUpdates(packages, includePrerelease, token);
}
/// <inheritdoc />
public void DownloadNupkgToFile(INugetPackageIdentifier package, string outputFilePath, string downloadUrlHint)
{
if (string.IsNullOrEmpty(downloadUrlHint))
{
downloadUrlHint = GetNuPkgFilePath(package);
}
File.Copy(downloadUrlHint, outputFilePath, true);
}
/// <inheritdoc />
public void Dispose()
{
// nothing to dispose
}
[NotNull]
private string GetNuPkgFilePath([NotNull] INugetPackageIdentifier package)
{
if (package.HasVersionRange)
{
throw new InvalidOperationException($"The package '{package}' has a version range which is not supported for this function.");
}
var localPackagePath = Path.Combine(ExpandedPath, $"{package.Id}.{package.Version}.nupkg");
if (!File.Exists(localPackagePath))
{
// Hierarchical folder structures are supported in NuGet 3.3+.
// └─<packageID>
// └─<version>
// └─<packageID>.<version>.nupkg
localPackagePath = Path.Combine(ExpandedPath, package.Id, package.Version, $"{package.Id}.{package.Version}.nupkg");
}
return localPackagePath;
}
/// <summary>
/// Gets a list of all available packages from a local source (not a web server) that match the given filters.
/// </summary>
/// <param name="searchTerm">The search term to use to filter packages. Defaults to the empty string.</param>
/// <param name="flattenAllVersions">True to include older versions that are not the latest version.</param>
/// <param name="includePrerelease">True to include prerelease packages (alpha, beta, etc).</param>
/// <param name="numberToSkip">The number of packages to skip before fetching.</param>
/// <returns>The list of available packages.</returns>
[NotNull]
[ItemNotNull]
private List<INugetPackage> GetLocalPackages(
string searchTerm = "",
bool flattenAllVersions = false,
bool includePrerelease = false,
int numberToSkip = 0)
{
if (string.IsNullOrEmpty(searchTerm))
{
searchTerm = "*";
}
var localPackages = new List<INugetPackage>();
if (numberToSkip != 0)
{
// we return the entire list the first time, so no more to add
return localPackages;
}
var path = ExpandedPath;
if (Directory.Exists(path))
{
var packagePaths = Directory.EnumerateFiles(path, $"{searchTerm}.nupkg");
// Hierarchical folder structures are supported in NuGet 3.3+.
// └─<packageID>
// └─<version>
// └─<packageID>.<version>.nupkg
var packagesFromFolders = Directory.EnumerateDirectories(path, searchTerm)
.SelectMany(Directory.EnumerateDirectories)
.SelectMany(versionFolder => Directory.EnumerateFiles(versionFolder, "*.nupkg"));
foreach (var packagePath in packagePaths.Concat(packagesFromFolders))
{
var package = NugetPackageLocal.FromNupkgFile(packagePath, this);
if (package.IsPrerelease && !includePrerelease)
{
// if it's a prerelease package and we aren't supposed to return prerelease packages, just skip it
continue;
}
if (flattenAllVersions)
{
// to flatten all versions we simply add it and move on
localPackages.Add(package);
continue;
}
var existingPackage = localPackages.Find(x => x.Id == package.Id);
if (existingPackage != null)
{
// there is already a package with the same ID in the list
if (existingPackage.CompareTo(package) < 0)
{
// if the current package is newer than the existing package, swap them
localPackages.Remove(existingPackage);
localPackages.Add(package);
package.Versions.AddRange(existingPackage.Versions);
}
else
{
// otherwise, just add the current package version to the existing package
existingPackage.Versions.Add(package.PackageVersion);
}
}
else
{
// there is no package with the same ID in the list yet
localPackages.Add(package);
}
}
}
else
{
Debug.LogErrorFormat("Local folder not found: {0}", path);
}
return localPackages;
}
/// <summary>
/// Gets a list of available packages from a local source (not a web server) that are versions / upgrade or downgrade of the given list of installed
/// packages.
/// </summary>
/// <param name="packages">The list of packages to use to find updates.</param>
/// <param name="includePrerelease">True to include prerelease packages (alpha, beta, etc).</param>
/// <param name="token"></param>
/// <returns>A list of all updates / downgrades available.</returns>
[NotNull]
[ItemNotNull]
private List<INugetPackage> GetLocalUpdates(
[NotNull] [ItemNotNull] IEnumerable<INugetPackage> packages,
bool includePrerelease = false,
CancellationToken token = default)
{
var updates = new List<INugetPackage>();
foreach (var packageToSearch in packages)
{
token.ThrowIfCancellationRequested();
var availablePackages = GetLocalPackages($"{packageToSearch.Id}*", false, includePrerelease);
foreach (var availablePackage in availablePackages)
{
if (packageToSearch.Id.Equals(availablePackage.Id, StringComparison.OrdinalIgnoreCase))
{
// keep the manually installed state
availablePackage.IsManuallyInstalled = packageToSearch.IsManuallyInstalled;
updates.Add(availablePackage);
}
}
}
return updates;
}
}
}