|
// 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.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NuGet.Configuration;
namespace NuGet.Protocol.Core.Types
{
/// <summary>
/// Represents a Server endpoint. Exposes methods to get a specific resource such as Search, Metrics service
/// and so on for the given server endpoint.
/// </summary>
public class SourceRepository
{
internal const int ProviderCacheTypes = 25;
private readonly Dictionary<Type, IReadOnlyList<INuGetResourceProvider>> _providerCache;
private readonly PackageSource _source;
/// <summary>
/// Pre-determined feed type.
/// </summary>
public FeedType FeedTypeOverride { get; }
/// <summary>
/// Source Repository
/// </summary>
/// <param name="source">source url</param>
/// <param name="providers">Resource providers</param>
public SourceRepository(PackageSource source, IEnumerable<INuGetResourceProvider> providers)
: this(source, providers.Select(p => new Lazy<INuGetResourceProvider>(() => p)))
{
}
/// <summary>
/// Source Repository
/// </summary>
/// <param name="source">source url</param>
/// <param name="providers">Resource providers</param>
public SourceRepository(PackageSource source, IEnumerable<Lazy<INuGetResourceProvider>> providers)
: this(source, providers, GetFeedType(source))
{
}
/// <summary>
/// Source Repository
/// </summary>
/// <param name="source">source url</param>
/// <param name="providers">Resource providers</param>
/// <param name="feedTypeOverride">Restrict the source to this feed type.</param>
public SourceRepository(
PackageSource source,
IEnumerable<Lazy<INuGetResourceProvider>> providers,
FeedType feedTypeOverride)
: this()
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
if (providers == null)
{
throw new ArgumentNullException(nameof(providers));
}
_source = source;
_providerCache = Init(providers);
FeedTypeOverride = feedTypeOverride;
}
/// <summary>
/// Internal default constructor
/// </summary>
protected SourceRepository()
{
}
public override string ToString()
{
return _source.Name;
}
/// <summary>
/// Package source
/// </summary>
public virtual PackageSource PackageSource
{
get { return _source; }
}
/// <summary>
/// Find the FeedType of the source. If overridden FeedTypeOverride is returned.
/// </summary>
public virtual async Task<FeedType> GetFeedType(CancellationToken token)
{
if (FeedTypeOverride == FeedType.Undefined)
{
var resource = await GetResourceAsync<FeedTypeResource>(token);
return resource.FeedType;
}
else
{
return FeedTypeOverride;
}
}
/// <summary>
/// Returns a resource from the SourceRepository if it exists.
/// </summary>
/// <typeparam name="T">Expected resource type</typeparam>
/// <returns>Null if the resource does not exist</returns>
public virtual T GetResource<T>() where T : class, INuGetResource
{
return GetResource<T>(CancellationToken.None);
}
/// <summary>
/// Returns a resource from the SourceRepository if it exists.
/// </summary>
/// <typeparam name="T">Expected resource type</typeparam>
/// <returns>Null if the resource does not exist</returns>
public virtual T GetResource<T>(CancellationToken token) where T : class, INuGetResource
{
var task = GetResourceAsync<T>(token);
task.Wait(token);
return task.Result;
}
/// <summary>
/// Returns a resource from the SourceRepository if it exists.
/// </summary>
/// <typeparam name="T">Expected resource type</typeparam>
/// <returns>Null if the resource does not exist</returns>
public virtual async Task<T> GetResourceAsync<T>() where T : class, INuGetResource
{
return await GetResourceAsync<T>(CancellationToken.None);
}
/// <summary>
/// Returns a resource from the SourceRepository if it exists.
/// </summary>
/// <typeparam name="T">Expected resource type</typeparam>
/// <returns>Null if the resource does not exist</returns>
public virtual async Task<T> GetResourceAsync<T>(CancellationToken token) where T : class, INuGetResource
{
var resourceType = typeof(T);
IReadOnlyList<INuGetResourceProvider> possible;
if (_providerCache.TryGetValue(resourceType, out possible))
{
for (int i = 0; i < possible.Count; i++)
{
var provider = possible[i];
var result = await provider.TryCreate(this, token);
if (result.Item1)
{
return (T)result.Item2;
}
}
}
return null;
}
/// <summary>
/// Initialize provider cache
/// </summary>
/// <param name="providers"></param>
/// <returns></returns>
private static Dictionary<Type, IReadOnlyList<INuGetResourceProvider>> Init(IEnumerable<Lazy<INuGetResourceProvider>> providers)
{
var cache = new Dictionary<Type, IReadOnlyList<INuGetResourceProvider>>(ProviderCacheTypes);
foreach (var group in providers.GroupBy(p => p.Value.ResourceType))
{
cache.Add(group.Key, Sort(group));
}
return cache;
}
private static IReadOnlyList<INuGetResourceProvider>
Sort(IEnumerable<Lazy<INuGetResourceProvider>> group)
{
var items = new List<INuGetResourceProvider>(group.Count());
foreach (var lazy in group)
{
items.Add(lazy.Value);
}
// initial ordering to help make this deterministic
items.Sort((a, b) =>
{
int cmp = StringComparer.Ordinal.Compare(a.Name, b.Name);
if (cmp != 0) return cmp;
cmp = a.After.Count().CompareTo(b.After.Count());
if (cmp != 0) return cmp;
return a.Before.Count().CompareTo(b.Before.Count());
});
var comparer = ProviderComparer.Instance;
// List.Sort does not work when lists have unsolvable gaps, which can occur here
for (int start = 0; start < items.Count - 1; start++)
{
int bestIndex = start;
for (int i = start + 1; i < items.Count; i++)
{
if (comparer.Compare(items[i], items[bestIndex]) < 0)
{
bestIndex = i;
}
}
if (bestIndex != start)
{
(items[start], items[bestIndex]) = (items[bestIndex], items[start]);
}
}
return items;
}
/// <summary>
/// Get the feed type from the package source.
/// </summary>
private static FeedType GetFeedType(PackageSource source)
{
var feedTypeSource = source as FeedTypePackageSource;
return feedTypeSource?.FeedType ?? FeedType.Undefined;
}
}
}
|