diff --git a/src/ProjectFileTools.NuGetSearch/Contracts/IPackageInfo.cs b/src/ProjectFileTools.NuGetSearch/Contracts/IPackageInfo.cs index 9bce1dd..f74f63f 100644 --- a/src/ProjectFileTools.NuGetSearch/Contracts/IPackageInfo.cs +++ b/src/ProjectFileTools.NuGetSearch/Contracts/IPackageInfo.cs @@ -1,4 +1,5 @@ -using ProjectFileTools.NuGetSearch.Feeds; +using System.Collections.Generic; +using ProjectFileTools.NuGetSearch.Feeds; namespace ProjectFileTools.NuGetSearch.Contracts { @@ -24,6 +25,8 @@ public interface IPackageInfo string Tags { get; } + IReadOnlyList PackageTypes { get; } + FeedKind SourceKind { get; } } } diff --git a/src/ProjectFileTools.NuGetSearch/Contracts/IPackageQueryConfiguration.cs b/src/ProjectFileTools.NuGetSearch/Contracts/IPackageQueryConfiguration.cs index de574bf..b178acb 100644 --- a/src/ProjectFileTools.NuGetSearch/Contracts/IPackageQueryConfiguration.cs +++ b/src/ProjectFileTools.NuGetSearch/Contracts/IPackageQueryConfiguration.cs @@ -1,11 +1,13 @@ -namespace ProjectFileTools.NuGetSearch.Contracts +namespace ProjectFileTools.NuGetSearch.Contracts { public interface IPackageQueryConfiguration { - string CompatibiltyTarget { get; } + string CompatibilityTarget { get; } bool IncludePreRelease { get; } int MaxResults { get; } + + PackageType PackageType { get; } } } diff --git a/src/ProjectFileTools.NuGetSearch/Contracts/IPackageSearchManager.cs b/src/ProjectFileTools.NuGetSearch/Contracts/IPackageSearchManager.cs index e1a5a0f..1acff57 100644 --- a/src/ProjectFileTools.NuGetSearch/Contracts/IPackageSearchManager.cs +++ b/src/ProjectFileTools.NuGetSearch/Contracts/IPackageSearchManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using ProjectFileTools.NuGetSearch.Feeds; namespace ProjectFileTools.NuGetSearch.Contracts @@ -6,9 +6,9 @@ namespace ProjectFileTools.NuGetSearch.Contracts public interface IPackageSearchManager { - IPackageFeedSearchJob> SearchPackageNames(string prefix, string tfm); + IPackageFeedSearchJob> SearchPackageNames(string prefix, string tfm, string packageType = null); - IPackageFeedSearchJob> SearchPackageVersions(string packageName, string tfm); + IPackageFeedSearchJob> SearchPackageVersions(string packageName, string tfm, string packageType = null); IPackageFeedSearchJob SearchPackageInfo(string packageId, string version, string tfm); } diff --git a/src/ProjectFileTools.NuGetSearch/Contracts/PackageType.cs b/src/ProjectFileTools.NuGetSearch/Contracts/PackageType.cs new file mode 100644 index 0000000..5cb2230 --- /dev/null +++ b/src/ProjectFileTools.NuGetSearch/Contracts/PackageType.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; + +namespace ProjectFileTools.NuGetSearch.Contracts +{ + public class PackageType : IEquatable + { + public static IReadOnlyList DefaultList { get; } = new[] { KnownPackageType.Dependency }; + + public PackageType(string id, string version = null) + { + Name = id ?? throw new ArgumentNullException(nameof(id)); + Version = version; + } + public string Name { get; } + public string Version { get; } + + + public override bool Equals(object obj) => Equals(obj as PackageType); + + public bool Equals(PackageType other) => other is PackageType + && string.Equals(Name, other.Name, StringComparison.Ordinal) + && string.Equals(Version, other.Version, StringComparison.Ordinal); + + public override int GetHashCode() + { + int hashCode = -612338121; + hashCode = hashCode * -1521134295 + StringComparer.Ordinal.GetHashCode(Name); + if (Version is not null) { + hashCode = hashCode * -1521134295 + StringComparer.Ordinal.GetHashCode(Version); + } + return hashCode; + } + } + + public class KnownPackageType + { + public static PackageType Legacy { get; } = new PackageType("Legacy"); + public static PackageType DotnetCliTool { get; } = new PackageType("DotnetCliTool"); + public static PackageType Dependency { get; } = new PackageType("Dependency"); + public static PackageType DotnetTool { get; } = new PackageType("DotnetTool"); + public static PackageType SymbolsPackage { get; } = new PackageType("SymbolsPackage"); + public static PackageType DotnetPlatform { get; } = new PackageType("DotnetPlatform"); + public static PackageType MSBuildSdk { get; } = new PackageType("MSBuildSdk"); + } +} \ No newline at end of file diff --git a/src/ProjectFileTools.NuGetSearch/Feeds/Disk/NuGetPackageMatcher.cs b/src/ProjectFileTools.NuGetSearch/Feeds/Disk/NuGetPackageMatcher.cs index 978f55c..fd6bd4f 100644 --- a/src/ProjectFileTools.NuGetSearch/Feeds/Disk/NuGetPackageMatcher.cs +++ b/src/ProjectFileTools.NuGetSearch/Feeds/Disk/NuGetPackageMatcher.cs @@ -1,4 +1,6 @@ -using ProjectFileTools.NuGetSearch.Contracts; +using System.Linq; + +using ProjectFileTools.NuGetSearch.Contracts; using ProjectFileTools.NuGetSearch.IO; namespace ProjectFileTools.NuGetSearch.Feeds.Disk @@ -17,7 +19,16 @@ public static bool IsMatch(string dir, IPackageInfo info, IPackageQueryConfigura } } - return true; + if (queryConfiguration.PackageType != null) + { + //NOTE: can't find any info on how the version is supposed to be handled (or what it's even for), so use an exact match + if (!info.PackageTypes.Any(p => queryConfiguration.PackageType.Equals(p))) + { + return false; + } + } + + return true; } } } diff --git a/src/ProjectFileTools.NuGetSearch/Feeds/NuSpecReader.cs b/src/ProjectFileTools.NuGetSearch/Feeds/NuSpecReader.cs index 12591e4..71df1ac 100644 --- a/src/ProjectFileTools.NuGetSearch/Feeds/NuSpecReader.cs +++ b/src/ProjectFileTools.NuGetSearch/Feeds/NuSpecReader.cs @@ -1,4 +1,5 @@ -using System.Xml.Linq; +using System.Collections.Generic; +using System.Xml.Linq; using ProjectFileTools.NuGetSearch.Contracts; namespace ProjectFileTools.NuGetSearch.Feeds @@ -23,9 +24,26 @@ internal static IPackageInfo Read(string nuspec, FeedKind kind) XElement iconUrl = metadata?.Element (XName.Get ("iconUrl", ns.NamespaceName)); XElement tags = metadata?.Element(XName.Get("tags", ns.NamespaceName)); + List packageTypes = null; + XElement packageTypesEl = metadata?.Element(XName.Get("packageTypes", ns.NamespaceName)); + if (packageTypesEl != null) + { + var nameName = XName.Get("name"); + var versionName = XName.Get("version"); + packageTypes = new List(); + foreach (var packageType in packageTypesEl.Elements(XName.Get("packageType", ns.NamespaceName))) + { + var typeName = packageType.Attribute(nameName).Value; + var typeVersion = packageType.Attribute(versionName)?.Value; + packageTypes.Add (new PackageType(typeName, typeVersion)); + } + } + if (id != null) { - return new PackageInfo(id.Value, version?.Value, title?.Value, authors?.Value, summary?.Value, description?.Value, licenseUrl?.Value, projectUrl?.Value, iconUrl?.Value, tags?.Value, kind); + return new PackageInfo( + id.Value, version?.Value, title?.Value, authors?.Value, summary?.Value, description?.Value, + licenseUrl?.Value, projectUrl?.Value, iconUrl?.Value, tags?.Value, kind, packageTypes); } return null; diff --git a/src/ProjectFileTools.NuGetSearch/Feeds/PackageInfo.cs b/src/ProjectFileTools.NuGetSearch/Feeds/PackageInfo.cs index 8af2c38..9f43638 100644 --- a/src/ProjectFileTools.NuGetSearch/Feeds/PackageInfo.cs +++ b/src/ProjectFileTools.NuGetSearch/Feeds/PackageInfo.cs @@ -1,11 +1,13 @@ -using ProjectFileTools.NuGetSearch.Contracts; +using System.Collections.Generic; + +using ProjectFileTools.NuGetSearch.Contracts; namespace ProjectFileTools.NuGetSearch.Feeds { public class PackageInfo : IPackageInfo { - public PackageInfo(string id, string version, string title, string authors, string summary, string description, string licenseUrl, string projectUrl, string iconUrl, string tags, FeedKind sourceKind) + public PackageInfo(string id, string version, string title, string authors, string summary, string description, string licenseUrl, string projectUrl, string iconUrl, string tags, FeedKind sourceKind, IReadOnlyList packageTypes) { Id = id; Version = version; @@ -17,6 +19,7 @@ public PackageInfo(string id, string version, string title, string authors, stri SourceKind = sourceKind; IconUrl = iconUrl; Tags = tags; + PackageTypes = packageTypes ?? PackageType.DefaultList; } public string Id { get; } @@ -40,5 +43,7 @@ public PackageInfo(string id, string version, string title, string authors, stri public string Tags { get; } public FeedKind SourceKind { get; } + + public IReadOnlyList PackageTypes { get; } } } diff --git a/src/ProjectFileTools.NuGetSearch/Feeds/PackageQueryConfiguration.cs b/src/ProjectFileTools.NuGetSearch/Feeds/PackageQueryConfiguration.cs index 594b3aa..f6a4f98 100644 --- a/src/ProjectFileTools.NuGetSearch/Feeds/PackageQueryConfiguration.cs +++ b/src/ProjectFileTools.NuGetSearch/Feeds/PackageQueryConfiguration.cs @@ -1,34 +1,37 @@ -using ProjectFileTools.NuGetSearch.Contracts; +using ProjectFileTools.NuGetSearch.Contracts; namespace ProjectFileTools.NuGetSearch.Feeds { public class PackageQueryConfiguration : IPackageQueryConfiguration { - public PackageQueryConfiguration(string targetFrameworkMoniker, bool includePreRelease = true, int maxResults = 100) + public PackageQueryConfiguration(string targetFrameworkMoniker, bool includePreRelease = true, int maxResults = 100, PackageType packageType = null) { - CompatibiltyTarget = targetFrameworkMoniker; + CompatibilityTarget = targetFrameworkMoniker; IncludePreRelease = includePreRelease; MaxResults = maxResults; + PackageType = packageType; } - public string CompatibiltyTarget { get; } + public string CompatibilityTarget { get; } public bool IncludePreRelease { get; } public int MaxResults { get; } + public PackageType PackageType { get; } + public override int GetHashCode() { - return (CompatibiltyTarget?.GetHashCode() ?? 0) ^ IncludePreRelease.GetHashCode() ^ MaxResults.GetHashCode(); + return (CompatibilityTarget?.GetHashCode() ?? 0) ^ IncludePreRelease.GetHashCode() ^ MaxResults.GetHashCode() ^ (PackageType?.GetHashCode() ?? 0); } public override bool Equals(object obj) { - PackageQueryConfiguration cfg = obj as PackageQueryConfiguration; - return cfg != null - && string.Equals(CompatibiltyTarget, cfg.CompatibiltyTarget, System.StringComparison.Ordinal) + return obj is PackageQueryConfiguration cfg + && string.Equals(CompatibilityTarget, cfg.CompatibilityTarget, System.StringComparison.Ordinal) && IncludePreRelease == cfg.IncludePreRelease - && MaxResults == cfg.MaxResults; + && MaxResults == cfg.MaxResults + && (PackageType?.Equals(cfg.PackageType) ?? false); } } } \ No newline at end of file diff --git a/src/ProjectFileTools.NuGetSearch/Feeds/Web/NuGetV2ServiceFeed.cs b/src/ProjectFileTools.NuGetSearch/Feeds/Web/NuGetV2ServiceFeed.cs index ff54afc..40723ae 100644 --- a/src/ProjectFileTools.NuGetSearch/Feeds/Web/NuGetV2ServiceFeed.cs +++ b/src/ProjectFileTools.NuGetSearch/Feeds/Web/NuGetV2ServiceFeed.cs @@ -57,7 +57,7 @@ public async Task GetPackageNamesAsync(string prefix, } IReadOnlyList results = new List(); - string frameworkQuery = !string.IsNullOrEmpty(queryConfiguration.CompatibiltyTarget) ? $"&targetFramework={queryConfiguration.CompatibiltyTarget}" : ""; + string frameworkQuery = !string.IsNullOrEmpty(queryConfiguration.CompatibilityTarget) ? $"&targetFramework={queryConfiguration.CompatibilityTarget}" : ""; var serviceEndpoint = $"{_feed}/Search()"; Func queryFunc = x => $"{x}?searchTerm='{prefix}'{frameworkQuery}&includePrerelease={queryConfiguration.IncludePreRelease}&semVerLevel=2.0.0"; XDocument document = await ExecuteAutocompleteServiceQueryAsync(serviceEndpoint, queryFunc, cancellationToken).ConfigureAwait(false); @@ -101,7 +101,7 @@ public async Task GetPackageInfoAsync(string packageId, string ver var licenseUrl = GetPropertyValue(document, el, "LicenseUrl"); var iconUrl = GetPropertyValue(document, el, "IconUrl"); var tags = GetPropertyValue(document, el, "Tags"); - var packageInfo = new PackageInfo(id, version, title, authors, summary, description, licenseUrl, projectUrl, iconUrl, tags, _kind); + var packageInfo = new PackageInfo(id, version, title, authors, summary, description, licenseUrl, projectUrl, iconUrl, tags, _kind, null); return packageInfo; } diff --git a/src/ProjectFileTools.NuGetSearch/Feeds/Web/NuGetV3ServiceFeed.cs b/src/ProjectFileTools.NuGetSearch/Feeds/Web/NuGetV3ServiceFeed.cs index 9711d0c..fc560ec 100644 --- a/src/ProjectFileTools.NuGetSearch/Feeds/Web/NuGetV3ServiceFeed.cs +++ b/src/ProjectFileTools.NuGetSearch/Feeds/Web/NuGetV3ServiceFeed.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -192,10 +192,11 @@ public async Task GetPackageNamesAsync(string prefix, } IReadOnlyList results = new List(); - string frameworkQuery = !string.IsNullOrEmpty(queryConfiguration.CompatibiltyTarget) ? $"&supportedFramework={queryConfiguration.CompatibiltyTarget}" : ""; - const string autoCompleteServiceTypeIdentifier = "SearchAutocompleteService"; + string frameworkQuery = !string.IsNullOrEmpty(queryConfiguration.CompatibilityTarget) ? $"&supportedFramework={queryConfiguration.CompatibilityTarget}" : ""; + string packageTypeQuery = !string.IsNullOrEmpty(queryConfiguration.PackageType?.Name) ? $"&packageType={queryConfiguration.PackageType.Name}" : ""; + const string autoCompleteServiceTypeIdentifier = "SearchAutocompleteService/3.5.0"; List serviceEndpoints = await DiscoverEndpointsAsync(_feed, autoCompleteServiceTypeIdentifier, cancellationToken).ConfigureAwait(false); - Func queryFunc = x => $"{x}?q={prefix}{frameworkQuery}&take={queryConfiguration.MaxResults}&prerelease={queryConfiguration.IncludePreRelease}"; + Func queryFunc = x => $"{x}?q={prefix}&semVerLevel=2.0.0{frameworkQuery}{packageTypeQuery}&take={queryConfiguration.MaxResults}&prerelease={queryConfiguration.IncludePreRelease}"; JObject document = await ExecuteAutocompleteServiceQueryAsync(serviceEndpoints, queryFunc, cancellationToken).ConfigureAwait(false); if (document != null) @@ -294,7 +295,7 @@ public async Task GetPackageInfoAsync(string packageId, string ver licenseUrl = catalogEntry["licenseUrl"]?.ToString(); iconUrl = catalogEntry["iconUrl"]?.ToString(); tags = catalogEntry ["tags"]?.ToString (); - packageInfo = new PackageInfo(id, version, title, authors, summary, description, licenseUrl, projectUrl, iconUrl, tags, _kind); + packageInfo = new PackageInfo(id, version, title, authors, summary, description, licenseUrl, projectUrl, iconUrl, tags, _kind, null); return packageInfo; } } @@ -311,7 +312,7 @@ public async Task GetPackageInfoAsync(string packageId, string ver licenseUrl = catalogEntry["licenseUrl"]?.ToString(); iconUrl = catalogEntry["iconUrl"]?.ToString (); tags = catalogEntry["tags"]?.ToString(); - packageInfo = new PackageInfo(id, version, title, authors, summary, description, licenseUrl, projectUrl, iconUrl, tags, _kind); + packageInfo = new PackageInfo(id, version, title, authors, summary, description, licenseUrl, projectUrl, iconUrl, tags, _kind, null); bestSemanticVersion = currentVersion; } } @@ -329,7 +330,7 @@ public async Task GetPackageInfoAsync(string packageId, string ver public async Task GetPackageVersionsAsync(string id, IPackageQueryConfiguration queryConfiguration, CancellationToken cancellationToken) { IReadOnlyList results = new List(); - string frameworkQuery = !string.IsNullOrEmpty(queryConfiguration.CompatibiltyTarget) ? $"&supportedFramework={queryConfiguration.CompatibiltyTarget}" : ""; + string frameworkQuery = !string.IsNullOrEmpty(queryConfiguration.CompatibilityTarget) ? $"&supportedFramework={queryConfiguration.CompatibilityTarget}" : ""; const string autoCompleteServiceTypeIdentifier = "SearchAutocompleteService"; List serviceEndpoints = await DiscoverEndpointsAsync(_feed, autoCompleteServiceTypeIdentifier, cancellationToken).ConfigureAwait(false); Func queryFunc = x => $"{x}?id={id}{frameworkQuery}&take={queryConfiguration.MaxResults}&prerelease={queryConfiguration.IncludePreRelease}"; diff --git a/src/ProjectFileTools.NuGetSearch/Search/PackageSearchManager.cs b/src/ProjectFileTools.NuGetSearch/Search/PackageSearchManager.cs index 1c8aee3..bf40737 100644 --- a/src/ProjectFileTools.NuGetSearch/Search/PackageSearchManager.cs +++ b/src/ProjectFileTools.NuGetSearch/Search/PackageSearchManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; @@ -27,11 +27,18 @@ private class PackageNameQuery { private readonly int _hashCode; private readonly string _prefix; + private readonly string _tfm; + private readonly PackageType _packageType; - public PackageNameQuery(string prefix, string tfm) + public PackageNameQuery(string prefix, string tfm, PackageType packageType) { - _hashCode = StringComparer.OrdinalIgnoreCase.GetHashCode(prefix ?? "") ^ (tfm?.GetHashCode() ?? 0); + _hashCode = StringComparer.OrdinalIgnoreCase.GetHashCode(prefix ?? "") + ^ (tfm?.GetHashCode() ?? 0) + ^ (packageType?.GetHashCode() ?? 0); + _prefix = prefix; + _tfm = tfm; + _packageType = packageType; } public override int GetHashCode() @@ -39,23 +46,25 @@ public override int GetHashCode() return _hashCode; } - public override bool Equals(object obj) - { - PackageNameQuery q = obj as PackageNameQuery; - - return q != null && q._hashCode == _hashCode && string.Equals(_prefix, q._prefix, StringComparison.Ordinal); - } + public override bool Equals(object obj) => + obj is PackageNameQuery q + && q._hashCode == _hashCode + && string.Equals(_prefix, q._prefix, StringComparison.Ordinal) + && string.Equals(_tfm, q._tfm, StringComparison.Ordinal) + && (_packageType?.Equals(q._packageType) ?? false); } private class PackageVersionQuery { private readonly int _hashCode; private readonly string _packageName; + private readonly string _tfm; - public PackageVersionQuery(string packageName, string tfm) + public PackageVersionQuery (string packageName, string tfm) { _hashCode = StringComparer.OrdinalIgnoreCase.GetHashCode(packageName ?? "") ^ (tfm?.GetHashCode() ?? 0); _packageName = packageName; + _tfm = tfm; } public override int GetHashCode() @@ -63,34 +72,34 @@ public override int GetHashCode() return _hashCode; } - public override bool Equals(object obj) - { - PackageVersionQuery q = obj as PackageVersionQuery; - - return q != null && q._hashCode == _hashCode && string.Equals(_packageName, q._packageName, StringComparison.Ordinal); - } + public override bool Equals(object obj) => + obj is PackageVersionQuery q + && q._hashCode == _hashCode + && string.Equals(_packageName, q._packageName, StringComparison.Ordinal) + && string.Equals(_tfm, q._tfm, StringComparison.Ordinal); } - public IPackageFeedSearchJob> SearchPackageNames(string prefix, string tfm) + public IPackageFeedSearchJob> SearchPackageNames(string prefix, string tfm, string packageType = null) { - PackageQueryConfiguration config = new PackageQueryConfiguration(tfm); + var packageTypeObj = packageType != null? new PackageType(packageType, null) : null; + var config = new PackageQueryConfiguration(tfm, packageType: packageTypeObj); var bag = _cachedNameSearches.GetOrAdd(config, x => new ConcurrentDictionary>>()); - return bag.AddOrUpdate(new PackageNameQuery(prefix, tfm), x => SearchPackageNamesInternal(prefix, tfm, config), (x, e) => + return bag.AddOrUpdate(new PackageNameQuery(prefix, tfm, packageTypeObj), x => SearchPackageNamesInternal(prefix, config), (x, e) => { if (e.IsCancelled) { - return SearchPackageNamesInternal(prefix, tfm, config); + return SearchPackageNamesInternal(prefix, config); } return e; }); } - private IPackageFeedSearchJob> SearchPackageNamesInternal(string prefix, string tfm, IPackageQueryConfiguration config) + private IPackageFeedSearchJob> SearchPackageNamesInternal(string prefix, IPackageQueryConfiguration config) { - List>>>> searchTasks = new List>>>>(); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + var searchTasks = new List>>>>(); + var cancellationTokenSource = new CancellationTokenSource(); CancellationToken cancellationToken = cancellationTokenSource.Token; foreach (string feedSource in _feedRegistry.ConfiguredFeeds) @@ -132,9 +141,10 @@ private IReadOnlyList> TransformToPackageInfo(Task> SearchPackageVersions(string packageName, string tfm) + public IPackageFeedSearchJob> SearchPackageVersions(string packageName, string tfm, string packageType = null) { - PackageQueryConfiguration config = new PackageQueryConfiguration(tfm); + var packageTypeObj = packageType != null ? new PackageType (packageType, null) : null; + var config = new PackageQueryConfiguration (tfm, packageType: packageTypeObj); var bag = _cachedVersionSearches.GetOrAdd(config, x => new ConcurrentDictionary>>()); return bag.AddOrUpdate(new PackageVersionQuery(packageName, tfm), x => SearchPackageVersionsInternal(packageName, tfm, config), (x, e) =>