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

Support embedded package icons #469

Merged
merged 23 commits into from
Feb 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/BaGet.Azure/Search/AzureSearchService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,17 @@ public async Task<SearchResponse> SearchAsync(
});
}

var iconUrl = document.HasEmbeddedIcon
? _url.GetPackageIconDownloadUrl(document.Id, NuGetVersion.Parse(document.Version))
: document.IconUrl;

results.Add(new SearchResult
{
PackageId = document.Id,
Version = document.Version,
Description = document.Description,
Authors = document.Authors,
IconUrl = document.IconUrl,
IconUrl = iconUrl,
LicenseUrl = document.LicenseUrl,
ProjectUrl = document.ProjectUrl,
RegistrationIndexUrl = _url.GetRegistrationIndexUrl(document.Id),
Expand Down
1 change: 1 addition & 0 deletions src/BaGet.Azure/Search/IndexActionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ private IReadOnlyList<IndexAction<KeyedDocument>> AddOrUpdatePackage(
document.Version = latest.Version.ToFullString();
document.Description = latest.Description;
document.Authors = latest.Authors;
document.HasEmbeddedIcon = latest.HasEmbeddedIcon;
document.IconUrl = latest.IconUrlString;
document.LicenseUrl = latest.LicenseUrlString;
document.ProjectUrl = latest.ProjectUrlString;
Expand Down
1 change: 1 addition & 0 deletions src/BaGet.Azure/Search/PackageDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class PackageDocument : KeyedDocument
[IsSearchable]
public string Description { get; set; }
public string[] Authors { get; set; }
public bool HasEmbeddedIcon { get; set; }
public string IconUrl { get; set; }
public string LicenseUrl { get; set; }
public string ProjectUrl { get; set; }
Expand Down
1 change: 1 addition & 0 deletions src/BaGet.Azure/Table/PackageEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public PackageEntity()
public string Description { get; set; }
public long Downloads { get; set; }
public bool HasReadme { get; set; }
public bool HasEmbeddedIcon { get; set; }
public bool IsPrerelease { get; set; }
public string Language { get; set; }
public bool Listed { get; set; }
Expand Down
1 change: 1 addition & 0 deletions src/BaGet.Azure/Table/TableOperationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public TableOperation AddPackage(Package package)
Description = package.Description,
Downloads = package.Downloads,
HasReadme = package.HasReadme,
HasEmbeddedIcon = package.HasEmbeddedIcon,
IsPrerelease = package.IsPrerelease,
Language = package.Language,
Listed = package.Listed,
Expand Down
1 change: 1 addition & 0 deletions src/BaGet.Azure/Table/TablePackageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ private Package AsPackage(PackageEntity entity)
Description = entity.Description,
Downloads = entity.Downloads,
HasReadme = entity.HasReadme,
HasEmbeddedIcon = entity.HasEmbeddedIcon,
IsPrerelease = entity.IsPrerelease,
Language = entity.Language,
Listed = entity.Listed,
Expand Down
7 changes: 6 additions & 1 deletion src/BaGet.Azure/Table/TableSearchService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -234,16 +234,21 @@ private SearchResult ToSearchResult(IReadOnlyList<PackageEntity> packages)
if (latestVersion == null || version > latestVersion)
{
latest = package;
latestVersion = version;
}
}

var iconUrl = latest.HasEmbeddedIcon
? _url.GetPackageIconDownloadUrl(latest.Id, latestVersion)
: latest.IconUrl;

return new SearchResult
{
PackageId = latest.Id,
Version = latest.NormalizedVersion,
Description = latest.Description,
Authors = JsonConvert.DeserializeObject<string[]>(latest.Authors),
IconUrl = latest.IconUrl,
IconUrl = iconUrl,
LicenseUrl = latest.LicenseUrl,
ProjectUrl = latest.ProjectUrl,
RegistrationIndexUrl = _url.GetRegistrationIndexUrl(latest.Id),
Expand Down
15 changes: 15 additions & 0 deletions src/BaGet.Core.Server/BaGetUrlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,21 @@ public string GetPackageManifestDownloadUrl(string id, NuGetVersion version)
});
}

public string GetPackageIconDownloadUrl(string id, NuGetVersion version)
{
id = id.ToLowerInvariant();
var versionString = version.ToNormalizedString().ToLowerInvariant();

return _linkGenerator.GetUriByRouteValues(
_httpContextAccessor.HttpContext,
Routes.PackageDownloadIconRouteName,
values: new
{
Id = id,
Version = versionString
});
}

private string AbsoluteUrl(string relativePath)
{
var request = _httpContextAccessor.HttpContext.Request;
Expand Down
16 changes: 16 additions & 0 deletions src/BaGet.Core.Server/Controllers/PackageContentController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,21 @@ public async Task<IActionResult> DownloadReadmeAsync(string id, string version,

return File(readmeStream, "text/markdown");
}

public async Task<IActionResult> DownloadIconAsync(string id, string version, CancellationToken cancellationToken)
{
if (!NuGetVersion.TryParse(version, out var nugetVersion))
{
return NotFound();
}

var iconStream = await _content.GetPackageIconStreamOrNullAsync(id, nugetVersion, cancellationToken);
if (iconStream == null)
{
return NotFound();
}

return File(iconStream, "image/xyz");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ public static void MapPackageContentRoutes(this IEndpointRouteBuilder endpoints)
name: Routes.PackageDownloadReadmeRouteName,
pattern: "v3/package/{id}/{version}/readme",
defaults: new { controller = "PackageContent", action = "DownloadReadme" });

endpoints.MapControllerRoute(
name: Routes.PackageDownloadIconRouteName,
pattern: "v3/package/{id}/{version}/icon",
defaults: new { controller = "PackageContent", action = "DownloadIcon" });
}
}
}
1 change: 1 addition & 0 deletions src/BaGet.Core.Server/Routes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class Routes
public const string PackageDownloadRouteName = "package-download";
public const string PackageDownloadManifestRouteName = "package-download-manifest";
public const string PackageDownloadReadmeRouteName = "package-download-readme";
public const string PackageDownloadIconRouteName = "package-download-icon";
public const string SymbolDownloadRouteName = "symbol-download";
public const string PrefixedSymbolDownloadRouteName = "prefixed-symbol-download";
}
Expand Down
14 changes: 14 additions & 0 deletions src/BaGet.Core/Content/DefaultPackageContentService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,19 @@ public async Task<Stream> GetPackageReadmeStreamOrNullAsync(string id, NuGetVers

return await _storage.GetReadmeStreamAsync(id, version, cancellationToken);
}

public async Task<Stream> GetPackageIconStreamOrNullAsync(string id, NuGetVersion version, CancellationToken cancellationToken = default)
{
// Allow read-through caching if it is configured.
await _mirror.MirrorAsync(id, version, cancellationToken);

var package = await _packages.FindOrNullAsync(id, version, includeUnlisted: true, cancellationToken);
if (!package.HasEmbeddedIcon)
{
return null;
}

return await _storage.GetIconStreamAsync(id, version, cancellationToken);
}
}
}
16 changes: 15 additions & 1 deletion src/BaGet.Core/Content/IPackageContentService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace BaGet.Core.Content
{
/// <summary>
/// The Package Content resource, used to download NuGet packages and to fetch other metadata.
///
///
/// See: https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource
/// </summary>
public interface IPackageContentService
Expand Down Expand Up @@ -67,5 +67,19 @@ Task<Stream> GetPackageReadmeStreamOrNullAsync(
string id,
NuGetVersion version,
CancellationToken cancellationToken);

/// <summary>
/// Download a package's icon, or null if the package or icon does not exist.
/// </summary>
/// <param name="id">The package id.</param>
/// <param name="version">The package's version.</param>
/// <param name="cancellationToken">A token to cancel the task.</param>
/// <returns>
/// The package's icon stream, or null if the package or icon does not exist. The stream may not be seekable.
/// </returns>
Task<Stream> GetPackageIconStreamOrNullAsync(
string id,
NuGetVersion version,
CancellationToken cancellationToken);
}
}
1 change: 1 addition & 0 deletions src/BaGet.Core/Entities/Package.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public NuGetVersion Version
public string Description { get; set; }
public long Downloads { get; set; }
public bool HasReadme { get; set; }
public bool HasEmbeddedIcon { get; set; }
public bool IsPrerelease { get; set; }
public string ReleaseNotes { get; set; }
public string Language { get; set; }
Expand Down
11 changes: 11 additions & 0 deletions src/BaGet.Core/Extensions/PackageArchiveReaderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ public static class PackageArchiveReaderExtensions
public static bool HasReadme(this PackageArchiveReader package)
=> package.GetFiles().Any(ReadmeFileNames.Contains);

public static bool HasEmbeddedIcon(this PackageArchiveReader package)
=> !string.IsNullOrEmpty(package.NuspecReader.GetIcon());

public async static Task<Stream> GetReadmeAsync(
this PackageArchiveReader package,
CancellationToken cancellationToken)
Expand All @@ -44,6 +47,13 @@ public async static Task<Stream> GetReadmeAsync(
throw new InvalidOperationException("Package does not have a readme!");
}

public async static Task<Stream> GetIconAsync(
this PackageArchiveReader package,
CancellationToken cancellationToken)
{
return await package.GetStreamAsync(package.NuspecReader.GetIcon(), cancellationToken);
}

public static Package GetPackageMetadata(this PackageArchiveReader packageReader)
{
var nuspec = packageReader.NuspecReader;
Expand All @@ -57,6 +67,7 @@ public static Package GetPackageMetadata(this PackageArchiveReader packageReader
Authors = ParseAuthors(nuspec.GetAuthors()),
Description = nuspec.GetDescription(),
HasReadme = packageReader.HasReadme(),
HasEmbeddedIcon = packageReader.HasEmbeddedIcon(),
IsPrerelease = nuspec.GetVersion().IsPrerelease,
Language = nuspec.GetLanguage() ?? string.Empty,
ReleaseNotes = nuspec.GetReleaseNotes() ?? string.Empty,
Expand Down
7 changes: 7 additions & 0 deletions src/BaGet.Core/IUrlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,12 @@ public interface IUrlGenerator
/// <param name="id">The package's ID</param>
/// <param name="version">The package's version</param>
string GetPackageManifestDownloadUrl(string id, NuGetVersion version);

/// <summary>
/// Get the URL to download a package icon.
/// </summary>
/// <param name="id">The package's ID</param>
/// <param name="version">The package's version</param>
string GetPackageIconDownloadUrl(string id, NuGetVersion version);
}
}
12 changes: 12 additions & 0 deletions src/BaGet.Core/Indexing/PackageIndexingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public async Task<PackageIndexingResult> IndexAsync(Stream packageStream, Cancel
Package package;
Stream nuspecStream;
Stream readmeStream;
Stream iconStream;

try
{
Expand All @@ -54,6 +55,16 @@ public async Task<PackageIndexingResult> IndexAsync(Stream packageStream, Cancel
{
readmeStream = null;
}

if (package.HasEmbeddedIcon)
{
iconStream = await packageReader.GetIconAsync(cancellationToken);
iconStream = await iconStream.AsTemporaryFileStreamAsync();
}
else
{
iconStream = null;
}
}
}
catch (Exception e)
Expand Down Expand Up @@ -91,6 +102,7 @@ await _storage.SavePackageContentAsync(
packageStream,
nuspecStream,
readmeStream,
iconStream,
cancellationToken);
}
catch (Exception e)
Expand Down
4 changes: 3 additions & 1 deletion src/BaGet.Core/Metadata/RegistrationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ private RegistrationIndexPageItem ToRegistrationIndexPageItem(Package package) =
Description = package.Description,
Downloads = package.Downloads,
HasReadme = package.HasReadme,
IconUrl = package.IconUrlString,
IconUrl = package.HasEmbeddedIcon
? _url.GetPackageIconDownloadUrl(package.Id, package.Version)
: package.IconUrlString,
Language = package.Language,
LicenseUrl = package.LicenseUrlString,
Listed = package.Listed,
Expand Down
5 changes: 4 additions & 1 deletion src/BaGet.Core/Search/DatabaseSearchService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,17 @@ public async Task<SearchResponse> SearchAsync(
{
var versions = package.OrderByDescending(p => p.Version).ToList();
var latest = versions.First();
var iconUrl = latest.HasEmbeddedIcon
? _url.GetPackageIconDownloadUrl(latest.Id, latest.Version)
: latest.IconUrlString;

result.Add(new SearchResult
{
PackageId = latest.Id,
Version = latest.Version.ToFullString(),
Description = latest.Description,
Authors = latest.Authors,
IconUrl = latest.IconUrlString,
IconUrl = iconUrl,
LicenseUrl = latest.LicenseUrlString,
ProjectUrl = latest.ProjectUrlString,
RegistrationIndexUrl = _url.GetRegistrationIndexUrl(latest.Id),
Expand Down
4 changes: 4 additions & 0 deletions src/BaGet.Core/Storage/IPackageStorageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ public interface IPackageStorageService
/// <param name="packageStream">The package's nupkg stream.</param>
/// <param name="nuspecStream">The package's nuspec stream.</param>
/// <param name="readmeStream">The package's readme stream, or null if none.</param>
/// <param name="iconStream">The package's icon stream, or null if none.</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task SavePackageContentAsync(
Package package,
Stream packageStream,
Stream nuspecStream,
Stream readmeStream,
Stream iconStream,
CancellationToken cancellationToken);

/// <summary>
Expand Down Expand Up @@ -55,6 +57,8 @@ Task SavePackageContentAsync(
/// <returns>The package's readme stream.</returns>
Task<Stream> GetReadmeStreamAsync(string id, NuGetVersion version, CancellationToken cancellationToken);

Task<Stream> GetIconStreamAsync(string id, NuGetVersion version, CancellationToken cancellationToken);

/// <summary>
/// Remove a package's content from storage. This operation SHOULD succeed
/// even if the package does not exist.
Expand Down
Loading