Skip to content

Commit

Permalink
(NuGet#9) Add methods for Count of packages from search
Browse files Browse the repository at this point in the history
In addition to being able to list out the packages from a search on a
feed, it is also necessary to know the numbers of packages that are
available, so that correct paging can be applied in applications like
Chocolatey GUI.  This commit add a SearchCountAsync method with an
implementation for for v2 and v3 feeds. It is believed that the v3 feed
will need additional work, but this implementation is aimed at getting
things working in Chocolatey GUI, when using v2 feeds, so additional
work will be done in follow up commits.

Co-authored-by: TheCakeIsNaOH <[email protected]>
  • Loading branch information
gep13 and TheCakeIsNaOH committed Feb 3, 2023
1 parent d28424e commit 6da318d
Show file tree
Hide file tree
Showing 11 changed files with 289 additions and 4 deletions.
3 changes: 2 additions & 1 deletion src/NuGet.Core/NuGet.Protocol/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,6 @@
[assembly: SuppressMessage("Build", "CA1303:Method 'string V2FeedQueryBuilder.BuildGetPackageUri(PackageIdentity package)' passes a literal string as parameter 'message' of a call to 'ArgumentException.ArgumentException(string message)'. Retrieve the following string(s) from a resource table instead: \"Version\".", Justification = "<Pending>", Scope = "member", Target = "~M:NuGet.Protocol.V2FeedQueryBuilder.BuildGetPackageUri(NuGet.Packaging.Core.PackageIdentity)~System.String")]
[assembly: SuppressMessage("Build", "CA1822:Member BuildOrderBy does not access instance data and can be marked as static (Shared in VisualBasic)", Justification = "<Pending>", Scope = "member", Target = "~M:NuGet.Protocol.V2FeedQueryBuilder.BuildOrderBy(System.Nullable{NuGet.Protocol.Core.Types.SearchOrderBy})~System.String")]
[assembly: SuppressMessage("Build", "CA1822:Member BuildPropertyFilter does not access instance data and can be marked as static (Shared in VisualBasic)", Justification = "<Pending>", Scope = "member", Target = "~M:NuGet.Protocol.V2FeedQueryBuilder.BuildPropertyFilter(System.Nullable{NuGet.Protocol.Core.Types.SearchFilterType})~System.String")]
[assembly: SuppressMessage("Build", "CA1308:In method 'BuildSearchUri', replace the call to 'ToLowerInvariant' with 'ToUpperInvariant'.", Justification = "<Pending>", Scope = "member", Target = "~M:NuGet.Protocol.V2FeedQueryBuilder.BuildSearchUri(System.String,NuGet.Protocol.Core.Types.SearchFilter,System.Int32,System.Int32)~System.String")]
[assembly: SuppressMessage("Build", "CA1062:In externally visible method 'string V2FeedQueryBuilder.BuildSearchUri(string searchTerm, SearchFilter filters, int skip, int take)', validate parameter 'filters' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "<Pending>", Scope = "member", Target = "~M:NuGet.Protocol.V2FeedQueryBuilder.BuildSearchUri(System.String,NuGet.Protocol.Core.Types.SearchFilter,System.Int32,System.Int32)~System.String")]
[assembly: SuppressMessage("Build", "CA1822:Member BuildSkip does not access instance data and can be marked as static (Shared in VisualBasic)", Justification = "<Pending>", Scope = "member", Target = "~M:NuGet.Protocol.V2FeedQueryBuilder.BuildSkip(System.Nullable{System.Int32})~System.String")]
[assembly: SuppressMessage("Build", "CA1822:Member BuildTop does not access instance data and can be marked as static (Shared in VisualBasic)", Justification = "<Pending>", Scope = "member", Target = "~M:NuGet.Protocol.V2FeedQueryBuilder.BuildTop(System.Nullable{System.Int32})~System.String")]
Expand Down Expand Up @@ -277,6 +276,8 @@
[assembly: SuppressMessage("Design", "CA1056:Uri properties should not be strings", Justification = "<Pending>", Scope = "member", Target = "~P:NuGet.Protocol.V2FeedPackageInfo.MailingListUrl")]
[assembly: SuppressMessage("Design", "CA1056:Uri properties should not be strings", Justification = "<Pending>", Scope = "member", Target = "~P:NuGet.Protocol.V2FeedPackageInfo.PackageSourceUrl")]
[assembly: SuppressMessage("Design", "CA1056:Uri properties should not be strings", Justification = "<Pending>", Scope = "member", Target = "~P:NuGet.Protocol.V2FeedPackageInfo.ProjectSourceUrl")]
[assembly: SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "<Pending>", Scope = "member", Target = "~M:NuGet.Protocol.V2FeedQueryBuilder.BuildSearchUri(System.String,NuGet.Protocol.Core.Types.SearchFilter,System.Int32,System.Int32,System.Boolean)~System.String")]
[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "<Pending>", Scope = "member", Target = "~F:NuGet.Protocol.V2FeedQueryBuilder._propertiesToSearch")]

//////////////////////////////////////////////////////////
// End - Chocolatey Specific Modification
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
using System.Threading.Tasks;
using NuGet.Protocol.Core.Types;

//////////////////////////////////////////////////////////
// Start - Chocolatey Specific Modification
//////////////////////////////////////////////////////////
using NuGet.Common;
//////////////////////////////////////////////////////////
// End - Chocolatey Specific Modification
//////////////////////////////////////////////////////////

namespace NuGet.Protocol
{
public class PackageSearchResourceV2Feed : PackageSearchResource
Expand Down Expand Up @@ -67,5 +75,22 @@ public override async Task<IEnumerable<IPackageSearchMetadata>> SearchAsync(

return results.ToList();
}

//////////////////////////////////////////////////////////
// Start - Chocolatey Specific Modification
//////////////////////////////////////////////////////////

public override async Task<int> SearchCountAsync(string searchTerm, SearchFilter filters, ILogger log, CancellationToken cancellationToken)
{
return await _feedParser.SearchCountAsync(
searchTerm,
filters,
log,
cancellationToken);
}

//////////////////////////////////////////////////////////
// End - Chocolatey Specific Modification
//////////////////////////////////////////////////////////
}
}
65 changes: 65 additions & 0 deletions src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,71 @@ public async Task<IReadOnlyList<V2FeedPackageInfo>> Search(
return page.Items;
}

//////////////////////////////////////////////////////////
// Start - Chocolatey Specific Modification
//////////////////////////////////////////////////////////

public async Task<int> SearchCountAsync(
string searchTerm,
SearchFilter filters,
ILogger log,
CancellationToken token)
{
var skip = 0;
var take = 1;
var relativeUri = _queryBuilder.BuildSearchUri(searchTerm, filters, skip, take, isCount: true);

var uri = string.Format(CultureInfo.InvariantCulture, "{0}{1}", _baseAddress, relativeUri);

int count = await _httpSource.ProcessResponseAsync(
new HttpSourceRequest(
() =>
{
var request = HttpRequestMessageFactory.Create(HttpMethod.Get, uri, log);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain"));
return request;
})
{
IsRetry = false
},
async response =>
{
if (response.StatusCode == HttpStatusCode.OK)
{
var networkStream = await response.Content.ReadAsStringAsync();
return Convert.ToInt32(networkStream);
}
else if (response.StatusCode == HttpStatusCode.NotFound)
{
// Treat "404 Not Found" as an empty response.
return 0;
}
else if (response.StatusCode == HttpStatusCode.NoContent)
{
// Always treat "204 No Content" as exactly that.
return 0;
}
else
{
throw new FatalProtocolException(string.Format(
CultureInfo.CurrentCulture,
Strings.Log_FailedToFetchV2Feed,
uri,
(int)response.StatusCode,
response.ReasonPhrase));
}
},
null,
log,
token);

return count;
}

//////////////////////////////////////////////////////////
// End - Chocolatey Specific Modification
//////////////////////////////////////////////////////////

public async Task<DownloadResourceResult> DownloadFromUrl(
PackageIdentity package,
Uri downloadUri,
Expand Down
45 changes: 42 additions & 3 deletions src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@
using System.Linq;
using System.Text;
using NuGet.Common;
using NuGet.Packaging.Core;
using NuGet.Protocol.Core.Types;

//////////////////////////////////////////////////////////
// Start - Chocolatey Specific Modification
//////////////////////////////////////////////////////////
using Chocolatey.NuGet.Frameworks;
//////////////////////////////////////////////////////////
// End - Chocolatey Specific Modification
//////////////////////////////////////////////////////////
using NuGet.Packaging.Core;
using NuGet.Protocol.Core.Types;

namespace NuGet.Protocol
{
Expand Down Expand Up @@ -46,7 +47,18 @@ public class V2FeedQueryBuilder
private const string GetSpecificPackageFormat = "/Packages(Id='{0}',Version='{1}')";

// constants for /Search() endpoint
private const string SearchEndpointFormat = "/Search()?{0}{1}searchTerm='{2}'&targetFramework='{3}'&includePrerelease={4}&$skip={5}&$top={6}&" + SemVerLevel;

//////////////////////////////////////////////////////////
// Start - Chocolatey Specific Modification
//////////////////////////////////////////////////////////

private const string SearchEndpointFormat = "/Search(){0}?{1}{2}searchTerm='{3}'&targetFramework='{4}'&includePrerelease={5}&$skip={6}&$top={7}&" + SemVerLevel;
private const string CountQueryString = "/$count";

//////////////////////////////////////////////////////////
// End - Chocolatey Specific Modification
//////////////////////////////////////////////////////////

private const string QueryDelimiter = "&";

// constants for /FindPackagesById() endpoint
Expand All @@ -73,12 +85,30 @@ public class V2FeedQueryBuilder
TagsProperty
};

//////////////////////////////////////////////////////////
// Start - Chocolatey Specific Modification
//////////////////////////////////////////////////////////

public string BuildSearchUri(
string searchTerm,
SearchFilter filters,
int skip,
int take)
{
return BuildSearchUri(searchTerm, filters, skip, take, false);
}

public string BuildSearchUri(
string searchTerm,
SearchFilter filters,
int skip,
int take,
bool isCount)
{
//////////////////////////////////////////////////////////
// End - Chocolatey Specific Modification
//////////////////////////////////////////////////////////

var shortFormTargetFramework = string.Join(
"|",
filters
Expand All @@ -99,6 +129,15 @@ public string BuildSearchUri(
var uri = string.Format(
CultureInfo.InvariantCulture,
SearchEndpointFormat,

//////////////////////////////////////////////////////////
// Start - Chocolatey Specific Modification
//////////////////////////////////////////////////////////
isCount ? CountQueryString : string.Empty,
//////////////////////////////////////////////////////////
// End - Chocolatey Specific Modification
//////////////////////////////////////////////////////////

filter != null ? filter + QueryDelimiter : string.Empty,
orderBy != null ? orderBy + QueryDelimiter : string.Empty,
UriUtility.UrlEncodeOdataParameter(searchTerm),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,5 +209,18 @@ private static IEnumerable<LocalPackageInfo> CollapseToHighestVersion(IEnumerabl

yield break;
}

//////////////////////////////////////////////////////////
// Start - Chocolatey Specific Modification
//////////////////////////////////////////////////////////

public override async Task<int> SearchCountAsync(string searchTerm, SearchFilter filters, ILogger log, CancellationToken cancellationToken)
{
return (await SearchAsync(searchTerm, filters, 0, int.MaxValue, log, cancellationToken)).Count();
}

//////////////////////////////////////////////////////////
// End - Chocolatey Specific Modification
//////////////////////////////////////////////////////////
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -288,14 +288,20 @@ NuGet.Protocol.V2FeedPackageInfo.ProjectSourceUrl.get -> string
NuGet.Protocol.V2FeedPackageInfo.ReleaseNotes.get -> string
NuGet.Protocol.V2FeedPackageInfo.V2FeedPackageInfo(NuGet.Packaging.Core.PackageIdentity identity, string title, string summary, string description, System.Collections.Generic.IEnumerable<string> authors, System.Collections.Generic.IEnumerable<string> owners, string iconUrl, string licenseUrl, string projectUrl, string reportAbuseUrl, string galleryDetailsUrl, string tags, System.DateTimeOffset? created, System.DateTimeOffset? lastEdited, System.DateTimeOffset? published, string dependencies, bool requireLicenseAccept, string downloadUrl, string downloadCount, string packageHash, string packageHashAlgorithm, NuGet.Versioning.NuGetVersion minClientVersion, long? packageSize, int? versionDownloadCount, bool isApproved, string packageStatus, string packageSubmittedStatus, string packageTestResultStatus, System.DateTime? packageTestResultStatusDate, string packageValidationResultStatus, System.DateTime? packageValidationResultDate, System.DateTime? packageCleanupResultDate, System.DateTime? packageReviewedDate, System.DateTime? packageApprovedDate, string packageReviewer, bool isDownloadCacheAvailable, System.DateTime? downloadCacheDate, string downloadCacheString, bool isLatestVersion, bool isAbsoluteLatestVersion, bool isPrerelease, string releaseNotes, string projectSourceUrl, string packageSourceUrl, string docsUrl, string mailingListUrl, string bugTrackerUrl, string downloadCacheStatus, string packageScanStatus, System.DateTime? packageScanResultDate, string packageScanFlagResult) -> void
NuGet.Protocol.V2FeedPackageInfo.VersionDownloadCount.get -> int?
NuGet.Protocol.V2FeedParser.SearchCountAsync(string searchTerm, NuGet.Protocol.Core.Types.SearchFilter filters, NuGet.Common.ILogger log, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task<int>
NuGet.Protocol.V2FeedQueryBuilder.BuildSearchUri(string searchTerm, NuGet.Protocol.Core.Types.SearchFilter filters, int skip, int take, bool isCount) -> string
abstract NuGet.Protocol.Core.Types.DependencyInfoResource.ResolvePackage(NuGet.Packaging.Core.PackageIdentity package, Chocolatey.NuGet.Frameworks.NuGetFramework projectFramework, NuGet.Protocol.Core.Types.SourceCacheContext cacheContext, NuGet.Common.ILogger log, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task<NuGet.Protocol.Core.Types.SourcePackageDependencyInfo>
abstract NuGet.Protocol.Core.Types.DependencyInfoResource.ResolvePackages(string packageId, Chocolatey.NuGet.Frameworks.NuGetFramework projectFramework, NuGet.Protocol.Core.Types.SourceCacheContext cacheContext, NuGet.Common.ILogger log, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<NuGet.Protocol.Core.Types.SourcePackageDependencyInfo>>
abstract NuGet.Protocol.Core.Types.PackageSearchResource.SearchCountAsync(string searchTerm, NuGet.Protocol.Core.Types.SearchFilter filters, NuGet.Common.ILogger log, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<int>
override NuGet.Protocol.DependencyInfoResourceV2Feed.ResolvePackage(NuGet.Packaging.Core.PackageIdentity package, Chocolatey.NuGet.Frameworks.NuGetFramework projectFramework, NuGet.Protocol.Core.Types.SourceCacheContext sourceCacheContext, NuGet.Common.ILogger log, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task<NuGet.Protocol.Core.Types.SourcePackageDependencyInfo>
override NuGet.Protocol.DependencyInfoResourceV2Feed.ResolvePackages(string packageId, Chocolatey.NuGet.Frameworks.NuGetFramework projectFramework, NuGet.Protocol.Core.Types.SourceCacheContext sourceCacheContext, NuGet.Common.ILogger log, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<NuGet.Protocol.Core.Types.SourcePackageDependencyInfo>>
override NuGet.Protocol.DependencyInfoResourceV3.ResolvePackage(NuGet.Packaging.Core.PackageIdentity package, Chocolatey.NuGet.Frameworks.NuGetFramework projectFramework, NuGet.Protocol.Core.Types.SourceCacheContext cacheContext, NuGet.Common.ILogger log, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task<NuGet.Protocol.Core.Types.SourcePackageDependencyInfo>
override NuGet.Protocol.DependencyInfoResourceV3.ResolvePackages(string packageId, Chocolatey.NuGet.Frameworks.NuGetFramework projectFramework, NuGet.Protocol.Core.Types.SourceCacheContext cacheContext, NuGet.Common.ILogger log, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<NuGet.Protocol.Core.Types.SourcePackageDependencyInfo>>
override NuGet.Protocol.LocalDependencyInfoResource.ResolvePackage(NuGet.Packaging.Core.PackageIdentity package, Chocolatey.NuGet.Frameworks.NuGetFramework projectFramework, NuGet.Protocol.Core.Types.SourceCacheContext sourceCacheContext, NuGet.Common.ILogger log, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task<NuGet.Protocol.Core.Types.SourcePackageDependencyInfo>
override NuGet.Protocol.LocalDependencyInfoResource.ResolvePackages(string packageId, Chocolatey.NuGet.Frameworks.NuGetFramework projectFramework, NuGet.Protocol.Core.Types.SourceCacheContext sourceCacheContext, NuGet.Common.ILogger log, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<NuGet.Protocol.Core.Types.SourcePackageDependencyInfo>>
override NuGet.Protocol.LocalPackageSearchResource.SearchCountAsync(string searchTerm, NuGet.Protocol.Core.Types.SearchFilter filters, NuGet.Common.ILogger log, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<int>
override NuGet.Protocol.PackageSearchResourceV2Feed.SearchCountAsync(string searchTerm, NuGet.Protocol.Core.Types.SearchFilter filters, NuGet.Common.ILogger log, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<int>
override NuGet.Protocol.PackageSearchResourceV3.SearchCountAsync(string searchTerm, NuGet.Protocol.Core.Types.SearchFilter filters, NuGet.Common.ILogger log, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<int>
override NuGet.Protocol.Plugins.PluginPackageReader.GetSupportedFrameworks() -> System.Collections.Generic.IEnumerable<Chocolatey.NuGet.Frameworks.NuGetFramework>
override NuGet.Protocol.Plugins.PluginPackageReader.GetSupportedFrameworksAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<Chocolatey.NuGet.Frameworks.NuGetFramework>>
override NuGet.Protocol.ChocolateyProgressStream.CanRead.get -> bool
Expand Down
Loading

0 comments on commit 6da318d

Please sign in to comment.