Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NuGet Symbol Server] Add download symbols support to Package details page #6320

Merged
merged 5 commits into from
Aug 16, 2018
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/NuGetGallery/App_Start/Routes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,12 @@ public static void RegisterApiV2Routes(RouteCollection routes)
defaults: new { controller = "Api", action = "GetPackageApi", version = UrlParameter.Optional },
constraints: new { httpMethod = new HttpMethodConstraint("GET") });

routes.MapRoute(
"v2" + RouteName.DownloadSymbolsPackage,
"api/v2/symbolpackage/{id}/{version}",
defaults: new { controller = "Api", action = "GetSymbolPackageApi", version = UrlParameter.Optional },
constraints: new { httpMethod = new HttpMethodConstraint("GET") });

routes.MapRoute(
"v2" + RouteName.PushPackageApi,
"api/v2/package",
Expand Down
120 changes: 81 additions & 39 deletions src/NuGetGallery/Controllers/ApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public partial class ApiController
public IReservedNamespaceService ReservedNamespaceService { get; set; }
public IPackageUploadService PackageUploadService { get; set; }
public IPackageDeleteService PackageDeleteService { get; set; }
public ISymbolPackageFileService SymbolPackageFileService { get; set; }
public ISymbolPackageService SymbolPackageService { get; set; }
public ISymbolPackageUploadService SymbolPackageUploadService { get; set; }
public IContentObjectService ContentObjectService { get; set; }
Expand Down Expand Up @@ -85,6 +86,7 @@ protected ApiController()
IReservedNamespaceService reservedNamespaceService,
IPackageUploadService packageUploadService,
IPackageDeleteService packageDeleteService,
ISymbolPackageFileService symbolPackageFileService,
ISymbolPackageService symbolPackageService,
ISymbolPackageUploadService symbolPackageUploadService,
IContentObjectService contentObjectService)
Expand All @@ -109,6 +111,7 @@ protected ApiController()
ReservedNamespaceService = reservedNamespaceService;
PackageUploadService = packageUploadService;
StatisticsService = null;
SymbolPackageFileService = symbolPackageFileService;
SymbolPackageService = symbolPackageService;
SymbolPackageUploadService = symbolPackageUploadService;
ContentObjectService = contentObjectService;
Expand Down Expand Up @@ -136,21 +139,35 @@ protected ApiController()
IReservedNamespaceService reservedNamespaceService,
IPackageUploadService packageUploadService,
IPackageDeleteService packageDeleteService,
ISymbolPackageFileService symbolPackageFileService,
ISymbolPackageService symbolPackageService,
ISymbolPackageUploadService symbolPackageUploadServivce,
IContentObjectService contentObjectService)
: this(apiScopeEvaluator, entitiesContext, packageService, packageFileService, userService, contentService,
indexingService, searchService, autoCuratePackage, statusService, messageService, auditingService,
configurationService, telemetryService, authenticationService, credentialBuilder, securityPolicies,
reservedNamespaceService, packageUploadService, packageDeleteService, symbolPackageService, symbolPackageUploadServivce,
contentObjectService)
reservedNamespaceService, packageUploadService, packageDeleteService, symbolPackageFileService,
symbolPackageService, symbolPackageUploadServivce, contentObjectService)
{
StatisticsService = statisticsService;
}


[HttpGet]
[ActionName("GetSymbolPackageApi")]
public virtual async Task<ActionResult> GetSymbolPackage(string id, string version)
{
return await GetPackageInternal(id, version, isSymbolPackage: true);
}

[HttpGet]
[ActionName("GetPackageApi")]
public virtual async Task<ActionResult> GetPackage(string id, string version)
{
return await GetPackageInternal(id, version, isSymbolPackage: false);
}

protected internal async Task<ActionResult> GetPackageInternal(string id, string version, bool isSymbolPackage = false)
{
// some security paranoia about URL hacking somehow creating e.g. open redirects
// validate user input: explicit calls to the same validators used during Package Registrations
Expand All @@ -160,61 +177,86 @@ public virtual async Task<ActionResult> GetPackage(string id, string version)
return new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, "The format of the package id is invalid");
}

// if version is non-null, check if it's semantically correct and normalize it.
if (!String.IsNullOrEmpty(version))
Package package = null;
try
{
NuGetVersion dummy;
if (!NuGetVersion.TryParse(version, out dummy))
if (!string.IsNullOrEmpty(version))
{
return new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, "The package version is not a valid semantic version");
}
// if version is non-null, check if it's semantically correct and normalize it.
NuGetVersion dummy;
if (!NuGetVersion.TryParse(version, out dummy))
{
return new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, "The package version is not a valid semantic version");
}

// Normalize the version
version = NuGetVersionFormatter.Normalize(version);
}
else
{
// If version is null, get the latest version from the database.
// This ensures that on package restore scenario where version will be non null, we don't hit the database.
try
// Normalize the version
version = NuGetVersionFormatter.Normalize(version);

if (isSymbolPackage)
{
package = PackageService.FindPackageByIdAndVersionStrict(id, version);
}
}
else
{
var package = PackageService.FindPackageByIdAndVersion(
// If version is null, get the latest version from the database.
// This ensures that on package restore scenario where version will be non null, we don't hit the database.
package = PackageService.FindPackageByIdAndVersion(
id,
version,
SemVerLevelKey.SemVer2,
allowPrerelease: false);

if (package == null)
{
return new HttpStatusCodeWithBodyResult(HttpStatusCode.NotFound, String.Format(CultureInfo.CurrentCulture, Strings.PackageWithIdAndVersionNotFound, id, version));
return new HttpStatusCodeWithBodyResult(HttpStatusCode.NotFound, string.Format(CultureInfo.CurrentCulture, Strings.PackageWithIdAndVersionNotFound, id, version));
}
version = package.NormalizedVersion;

version = package.NormalizedVersion;
}
catch (SqlException e)
{
QuietLog.LogHandledException(e);
}
catch (SqlException e)
{
QuietLog.LogHandledException(e);

// Database was unavailable and we don't have a version, return a 503
return new HttpStatusCodeWithBodyResult(HttpStatusCode.ServiceUnavailable, Strings.DatabaseUnavailable_TrySpecificVersion);
}
catch (DataException e)
{
QuietLog.LogHandledException(e);
// Database was unavailable and we don't have a version, return a 503
return new HttpStatusCodeWithBodyResult(HttpStatusCode.ServiceUnavailable, Strings.DatabaseUnavailable_TrySpecificVersion);
}
catch (DataException e)
{
QuietLog.LogHandledException(e);

// Database was unavailable and we don't have a version, return a 503
return new HttpStatusCodeWithBodyResult(HttpStatusCode.ServiceUnavailable, Strings.DatabaseUnavailable_TrySpecificVersion);
}
// Database was unavailable and we don't have a version, return a 503
return new HttpStatusCodeWithBodyResult(HttpStatusCode.ServiceUnavailable, Strings.DatabaseUnavailable_TrySpecificVersion);
}

if (ConfigurationService.Features.TrackPackageDownloadCountInLocalDatabase)
if (isSymbolPackage)
{
await PackageService.IncrementDownloadCountAsync(id, version);
var latestSymbolPackage = package?
.SymbolPackages
.OrderByDescending(sp => sp.Created)
.FirstOrDefault();

if (latestSymbolPackage == null || latestSymbolPackage.StatusKey != PackageStatus.Available)
{
return new HttpStatusCodeWithBodyResult(HttpStatusCode.NotFound, string.Format(CultureInfo.CurrentCulture, Strings.SymbolsPackage_PackageNotAvailable, id, version));
}

return await SymbolPackageFileService.CreateDownloadSymbolPackageActionResultAsync(
HttpContext.Request.Url,
id, version);
}
else
{
if (ConfigurationService.Features.TrackPackageDownloadCountInLocalDatabase)
{
await PackageService.IncrementDownloadCountAsync(id, version);
}

return await PackageFileService.CreateDownloadPackageActionResultAsync(
HttpContext.Request.Url,
id, version);
return await PackageFileService.CreateDownloadPackageActionResultAsync(
HttpContext.Request.Url,
id, version);
}
}

[HttpGet]
Expand Down Expand Up @@ -303,7 +345,7 @@ private async Task<HttpStatusCodeWithBodyResult> VerifyPackageKeyInternalAsync(U
if (package == null)
{
return new HttpStatusCodeWithBodyResult(
HttpStatusCode.NotFound, String.Format(CultureInfo.CurrentCulture, Strings.PackageWithIdAndVersionNotFound, id, version));
HttpStatusCode.NotFound, string.Format(CultureInfo.CurrentCulture, Strings.PackageWithIdAndVersionNotFound, id, version));
}

// Write an audit record
Expand Down Expand Up @@ -782,7 +824,7 @@ public virtual async Task<ActionResult> DeletePackage(string id, string version)
if (package == null)
{
return new HttpStatusCodeWithBodyResult(
HttpStatusCode.NotFound, String.Format(CultureInfo.CurrentCulture, Strings.PackageWithIdAndVersionNotFound, id, version));
HttpStatusCode.NotFound, string.Format(CultureInfo.CurrentCulture, Strings.PackageWithIdAndVersionNotFound, id, version));
}

// Check if the current user's scopes allow listing/unlisting the current package ID
Expand Down Expand Up @@ -814,7 +856,7 @@ public virtual async Task<ActionResult> PublishPackage(string id, string version
if (package == null)
{
return new HttpStatusCodeWithBodyResult(
HttpStatusCode.NotFound, String.Format(CultureInfo.CurrentCulture, Strings.PackageWithIdAndVersionNotFound, id, version));
HttpStatusCode.NotFound, string.Format(CultureInfo.CurrentCulture, Strings.PackageWithIdAndVersionNotFound, id, version));
}

// Check if the current user's scopes allow listing/unlisting the current package ID
Expand Down
1 change: 1 addition & 0 deletions src/NuGetGallery/RouteName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public static class RouteName
public const string Profile = "Profile";
public const string DisplayPackage = "package-route";
public const string DownloadPackage = "DownloadPackage";
public const string DownloadSymbolsPackage = "DownloadSymbolsPackage";
public const string DownloadNuGetExe = "DownloadNuGetExe";
public const string Home = "Home";
public const string Stats = "Stats";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ private static string GetContentType(string folderName)
switch (folderName)
{
case CoreConstants.PackagesFolderName:
case CoreConstants.SymbolPackagesFolderName:
return CoreConstants.PackageContentType;

case CoreConstants.DownloadsFolderName:
Expand Down
9 changes: 9 additions & 0 deletions src/NuGetGallery/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/NuGetGallery/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -949,4 +949,7 @@ If you would like to update the linked Microsoft account you can do so from the
<value>The previous package version '{0}' is author signed but the uploaded package is unsigned. To avoid this warning, sign the package before uploading.</value>
<comment>{0} is the previous package's normalized version.</comment>
</data>
<data name="SymbolsPackage_PackageNotAvailable" xml:space="preserve">
<value>No availabe symbol package found for ID {0} and version {1}.</value>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please verify text with PM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@diverdan92 - verify.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this displayed? Otherwise, verified.

Although, should it be 'symbols package' since the packages can contain multiple symbols?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is displayed when you try to download via rest endpoint and its not available, or the latest uploaded symbol package is validating/failed validation/deleted.

</data>
</root>
20 changes: 20 additions & 0 deletions src/NuGetGallery/UrlExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,26 @@ public static string PackageDefaultIcon(this UrlHelper url)
return version == null ? EnsureTrailingSlash(result) : result;
}

public static string SymbolPackageDownload(
this UrlHelper url,
int feedVersion,
string id,
string version,
bool relativeUrl = true)
{
string result = GetRouteLink(
url,
routeName: $"v{feedVersion}{RouteName.DownloadSymbolsPackage}",
relativeUrl: false,
routeValues: new RouteValueDictionary
{
{ "Id", id },
{ "Version", version }
});

// Ensure trailing slashes for versionless package URLs, as a fix for package filenames that look like known file extensions
return version == null ? EnsureTrailingSlash(result) : result;
}
public static string ExplorerDeepLink(
this UrlHelper url,
int feedVersion,
Expand Down
6 changes: 6 additions & 0 deletions src/NuGetGallery/ViewModels/DisplayPackageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ public DisplayPackageViewModel(Package package, User currentUser, IOrderedEnumer
PushedBy = GetPushedBy(package, currentUser);
PackageFileSize = package.PackageFileSize;

LatestSymbolPackage = package
.SymbolPackages
.OrderByDescending(sp => sp.Created)
.FirstOrDefault();

if (packageHistory.Any())
{
// calculate the number of days since the package registration was created
Expand Down Expand Up @@ -62,6 +67,7 @@ public DisplayPackageViewModel(Package package, User currentUser, string pushedB
public int DownloadsPerDay { get; private set; }
public int TotalDaysSinceCreated { get; private set; }
public long PackageFileSize { get; private set; }
public SymbolPackage LatestSymbolPackage { get; private set; }

public bool HasSemVer2Version { get; }
public bool HasSemVer2Dependency { get; }
Expand Down
12 changes: 11 additions & 1 deletion src/NuGetGallery/Views/Packages/DisplayPackage.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

var absolutePackageUrl = Url.Absolute(Url.Package(Model.Id));

var hasSymbolPackageAvailable = Model.LatestSymbolPackage != null && Model.LatestSymbolPackage.StatusKey == PackageStatus.Available;

PackageManagerViewModel[] packageManagers;

if (Model.IsDotnetToolPackageType)
Expand Down Expand Up @@ -633,9 +635,17 @@
{
<li>
<i class="ms-Icon ms-Icon--CloudDownload" aria-hidden="true"></i>
<a href="@Url.PackageDownload(2, Model.Id, Model.Version)" data-track="outbound-manual-download" title="Download the raw nupkg file." rel="nofollow">Download</a>
<a href="@Url.PackageDownload(2, Model.Id, Model.Version)" data-track="outbound-manual-download" title="Download the raw nupkg file." rel="nofollow">Download Package</a>
&nbsp;(@Model.PackageFileSize.ToUserFriendlyBytesLabel())
</li>
if (hasSymbolPackageAvailable)
{
<li>
<i class="ms-Icon ms-Icon--CloudDownload" aria-hidden="true"></i>
<a href="@Url.SymbolPackageDownload(2, Model.Id, Model.Version)" data-track="outbound-manual-download" title="Download the raw snupkg file." rel="nofollow">Download Symbols</a>
&nbsp;(@Model.LatestSymbolPackage.FileSize.ToUserFriendlyBytesLabel())
</li>
}
<li class="no-clickonce">
<i class="ms-Icon ms-Icon--OpenInNewWindow" aria-hidden="true"></i>
<a href="@Url.ExplorerDeepLink(2, Model.Id, Model.Version)" title="Explore the nupkg with the NuGet Package Explorer (IE only)" rel="nofollow">Open in Package Explorer</a>
Expand Down