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
{
///
/// Represents a NuGet Package Source that uses packages stored on a local or network drive.
///
[Serializable]
internal sealed class NugetPackageSourceLocal : INugetPackageSource
{
///
/// Initializes a new instance of the class.
///
/// The name of the package source.
/// The path to the package source.
public NugetPackageSourceLocal([NotNull] string name, [NotNull] string path)
{
Name = name;
SavedPath = path;
IsEnabled = true;
}
///
[field: SerializeField]
public string Name { get; set; }
///
[field: SerializeField]
public string SavedPath { get; set; }
///
public string SavedProtocolVersion
{
get => null;
set
{
// multiple sources can't have protocol version
}
}
///
[field: SerializeField]
public bool IsEnabled { get; set; }
///
public string UserName
{
get => null;
set
{
// local sources don't have credentials
}
}
///
public string SavedPassword
{
get => null;
set
{
// local sources don't have credentials
}
}
///
public bool SavedPasswordIsEncrypted
{
get => false;
set
{
// local sources don't have credentials
}
}
///
public bool HasPassword
{
get => false;
set
{
// local sources don't have credentials
}
}
///
/// Gets path, with the values of environment variables expanded.
///
[NotNull]
private string ExpandedPath
{
get
{
var path = Environment.ExpandEnvironmentVariables(SavedPath);
if (!Path.IsPathRooted(path))
{
path = Path.Combine(ConfigurationManager.NugetConfigFileDirectoryPath, path);
}
return path;
}
}
///
public List FindPackagesById(INugetPackageIdentifier package)
{
List 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 { localPackage };
}
else
{
foundPackages = new List();
}
}
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;
}
///
public INugetPackage GetSpecificPackage(INugetPackageIdentifier package)
{
// if multiple match we use the lowest version
return FindPackagesById(package).FirstOrDefault();
}
///
public Task> SearchAsync(
string searchTerm = "",
bool includePrerelease = false,
int numberToGet = 15,
int numberToSkip = 0,
CancellationToken cancellationToken = default)
{
return Task.FromResult(GetLocalPackages($"*{searchTerm}*", false, includePrerelease, numberToSkip));
}
///
public List GetUpdates(
IEnumerable packages,
bool includePrerelease = false,
string targetFrameworks = "",
string versionConstraints = "")
{
return GetLocalUpdates(packages, includePrerelease);
}
///
public void DownloadNupkgToFile(INugetPackageIdentifier package, string outputFilePath, string downloadUrlHint)
{
if (string.IsNullOrEmpty(downloadUrlHint))
{
downloadUrlHint = GetNuPkgFilePath(package);
}
File.Copy(downloadUrlHint, outputFilePath, true);
}
///
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 witch 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+.
// └─
// └─
// └─..nupkg
localPackagePath = Path.Combine(ExpandedPath, package.Id, package.Version, $"{package.Id}.{package.Version}.nupkg");
}
return localPackagePath;
}
///
/// Gets a list of all available packages from a local source (not a web server) that match the given filters.
///
/// The search term to use to filter packages. Defaults to the empty string.
/// True to include older versions that are not the latest version.
/// True to include prerelease packages (alpha, beta, etc).
/// The number of packages to skip before fetching.
/// The list of available packages.
[NotNull]
[ItemNotNull]
private List GetLocalPackages(
string searchTerm = "",
bool flattenAllVersions = false,
bool includePrerelease = false,
int numberToSkip = 0)
{
if (string.IsNullOrEmpty(searchTerm))
{
searchTerm = "*";
}
var localPackages = new List();
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+.
// └─
// └─
// └─..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;
}
///
/// 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.
///
/// The list of packages to use to find updates.
/// True to include prerelease packages (alpha, beta, etc).
/// A list of all updates / downgrades available.
[NotNull]
[ItemNotNull]
private List GetLocalUpdates([NotNull] [ItemNotNull] IEnumerable packages, bool includePrerelease = false)
{
var updates = new List();
foreach (var packageToSearch in packages)
{
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;
}
}
}