using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using NugetForUnity.Configuration;
using NugetForUnity.Models;
using UnityEngine;
namespace NugetForUnity.PackageSource
{
///
/// Online NuGet package source for NuGet API v3.
///
[Serializable]
internal sealed class NugetPackageSourceV3 : INugetPackageSource, ISerializationCallbackReceiver
{
///
/// Default value for .
///
public const int DefaultUpdateSearchBatchSize = 20;
///
/// Default value for .
///
public const bool DefaultSupportsPackageIdSearchFilter = true;
[NotNull]
private static readonly Dictionary ApiClientCache = new Dictionary();
[CanBeNull]
[NonSerialized]
private NugetApiClientV3 apiClient;
///
/// Initializes a new instance of the class.
///
/// The name of the package source.
/// The path to the package source.
/// The explicitly defined protocol version stored inside the 'NuGet.config'.
public NugetPackageSourceV3([NotNull] string name, [NotNull] string url, string savedProtocolVersion)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException($"'{nameof(name)}' cannot be null or empty.", nameof(name));
}
if (string.IsNullOrEmpty(url))
{
throw new ArgumentException($"'{nameof(url)}' cannot be null or empty.", nameof(url));
}
Name = name;
SavedPath = url;
SavedProtocolVersion = savedProtocolVersion;
IsEnabled = true;
InitializeApiClient();
}
///
/// Gets password, with the values of environment variables expanded.
///
[CanBeNull]
public string ExpandedPassword
{
get
{
if (SavedPassword == null)
{
return null;
}
var expandedPassword = Environment.ExpandEnvironmentVariables(SavedPassword);
return SavedPasswordIsEncrypted ? ConfigurationEncryptionHelper.DecryptString(expandedPassword) : expandedPassword;
}
}
///
[field: SerializeField]
public string Name { get; set; }
///
[field: SerializeField]
public string SavedPath { get; set; }
///
[field: SerializeField]
public string SavedProtocolVersion { get; set; }
///
public bool SavedPasswordIsEncrypted { get; set; }
///
[CanBeNull]
public string PackageDownloadUrlTemplateOverwrite
{
get => ApiClient.PackageDownloadUrlTemplateOverwrite;
set => ApiClient.PackageDownloadUrlTemplateOverwrite = value;
}
///
/// Gets or sets the size of each batch in witch available updates are fetched using the ,
/// to prevent the search query string to exceed the URI length limit we fetch the updates in groups. Defaults to 20.
///
[field: SerializeField]
public int UpdateSearchBatchSize { get; set; } = DefaultUpdateSearchBatchSize;
///
/// Gets or sets a value indicating whether the package registry supports querying a package by its id
/// using the syntax 'packageid:{packageId}'. This syntax is used to easily search for package updates.
/// If it is disabled package updates are searched by loading the information of all packages one by one.
///
public bool SupportsPackageIdSearchFilter { get; set; } = DefaultSupportsPackageIdSearchFilter;
///
[field: SerializeField]
public string UserName { get; set; }
///
[field: SerializeField]
public string SavedPassword { get; set; }
///
[field: SerializeField]
public bool IsEnabled { get; set; }
///
public bool HasPassword
{
get => SavedPassword != null;
set
{
if (value)
{
if (SavedPassword == null)
{
SavedPassword = string.Empty; // Initialize newly-enabled password to empty string.
}
}
else
{
SavedPassword = null; // Clear password to null when disabled.
}
}
}
///
/// Gets the lazy initialized API-client. We need this because can't be called in
/// .
///
[NotNull]
private NugetApiClientV3 ApiClient => apiClient ?? InitializeApiClient();
///
public List FindPackagesById(INugetPackageIdentifier package)
{
var fetchedPackage = Task.Run(() => ApiClient.GetPackageWithDetailsAsync(this, package, CancellationToken.None)).GetAwaiter().GetResult();
if (fetchedPackage is null)
{
return new List();
}
if (!package.HasVersionRange && fetchedPackage.Equals(package))
{
// exact match found
return new List { fetchedPackage };
}
var matchingVersion = fetchedPackage.Versions.FindLast(package.InRange);
if (matchingVersion == null)
{
// no matching version found
return new List();
}
// overwrite the version so it is installed
fetchedPackage.PackageVersion = matchingVersion;
return new List { fetchedPackage };
}
///
public INugetPackage GetSpecificPackage(INugetPackageIdentifier package)
{
return FindPackagesById(package).FirstOrDefault();
}
///
public List GetUpdates(
IEnumerable packages,
bool includePrerelease = false,
string targetFrameworks = "",
string versionConstraints = "")
{
var packagesToFetch = packages as IList ?? packages.ToList();
var packagesFromServer = Task.Run(
async () =>
{
if (SupportsPackageIdSearchFilter)
{
var updates = new List();
for (var i = 0; i < packagesToFetch.Count;)
{
var searchQueryBuilder = new StringBuilder();
for (var inner = 0; inner < UpdateSearchBatchSize && i < packagesToFetch.Count; inner++, i++)
{
if (i > 0)
{
searchQueryBuilder.Append(' ');
}
searchQueryBuilder.Append($"packageid:{packagesToFetch[i].Id}");
}
updates.AddRange(
await ApiClient.SearchPackageAsync(
this,
searchQueryBuilder.ToString(),
0,
0,
includePrerelease,
CancellationToken.None)
.ConfigureAwait(false));
}
if (updates.Count > packagesToFetch.Count)
{
Debug.LogWarningFormat(
"Fetching updates using a filter with the format 'packageid:{{packageId}}' resulted in more packages as requested. This probably means that the syntax is not supported by the package source {0}. So consider switching to a different 'update search mechanism' by disabling the '{1}' setting for this package source.",
SavedPath,
nameof(SupportsPackageIdSearchFilter));
}
return updates;
}
var fetchedPackages = await Task.WhenAll(
packagesToFetch.Select(package => ApiClient.GetPackageWithAllVersionsAsync(this, package, CancellationToken.None)))
.ConfigureAwait(false);
return fetchedPackages.ToList();
})
.GetAwaiter()
.GetResult();
CopyIsManuallyInstalled(packagesFromServer, packagesToFetch);
packagesFromServer.Sort();
return packagesFromServer;
}
///
public Task> SearchAsync(
string searchTerm = "",
bool includePrerelease = false,
int numberToGet = 15,
int numberToSkip = 0,
CancellationToken cancellationToken = default)
{
return ApiClient.SearchPackageAsync(this, searchTerm, numberToSkip, numberToGet, includePrerelease, cancellationToken);
}
///
public void DownloadNupkgToFile(INugetPackageIdentifier package, string outputFilePath, string downloadUrlHint)
{
Task.Run(() => ApiClient.DownloadNupkgToFileAsync(this, package, outputFilePath, downloadUrlHint)).GetAwaiter().GetResult();
}
///
public void Dispose()
{
apiClient?.Dispose();
ApiClientCache.Remove(SavedPath);
}
///
public void OnBeforeSerialize()
{
// do nothing
}
///
public void OnAfterDeserialize()
{
if (string.IsNullOrEmpty(SavedPassword))
{
SavedPassword = null;
}
}
///
[NotNull]
[ItemNotNull]
public Task> GetPackageDetailsAsync(
[NotNull] INugetPackageIdentifier package,
CancellationToken cancellationToken = default)
{
return ApiClient.GetPackageDetailsAsync(this, package, cancellationToken);
}
[NotNull]
private NugetApiClientV3 InitializeApiClient()
{
if (ApiClientCache.TryGetValue(SavedPath, out apiClient))
{
Debug.Assert(apiClient != null, nameof(apiClient) + " != null");
return apiClient;
}
apiClient = new NugetApiClientV3(SavedPath);
ApiClientCache.Add(SavedPath, apiClient);
return apiClient;
}
private void CopyIsManuallyInstalled(List newPackages, ICollection packagesToUpdate)
{
foreach (var newPackage in newPackages)
{
newPackage.IsManuallyInstalled =
packagesToUpdate.FirstOrDefault(packageToUpdate => packageToUpdate.Id.Equals(newPackage.Id, StringComparison.OrdinalIgnoreCase))
?.IsManuallyInstalled ??
false;
}
}
}
}