Skip to content
Draft
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
11 changes: 11 additions & 0 deletions src/BaGetter.Core/Content/DefaultPackageContentService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,15 @@ public async Task<Stream> GetPackageIconStreamOrNullAsync(string id, NuGetVersio

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

public async Task<Stream> GetPackageLicenseStreamOrNullAsync(string id, NuGetVersion version, CancellationToken cancellationToken = default)
{
var package = await _packages.FindPackageOrNullAsync(id, version, cancellationToken);
if (package == null || !package.HasEmbeddedLicense)
{
return null;
}

return await _storage.GetLicenseStreamAsync(id, version, package.LicenseFormatIsMarkdown, cancellationToken);
}
}
9 changes: 9 additions & 0 deletions src/BaGetter.Core/Content/IPackageContentService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,13 @@ Task<Stream> GetPackageIconStreamOrNullAsync(
string id,
NuGetVersion version,
CancellationToken cancellationToken);

/// <summary>
/// The package license if the license is an embedded license, or null if the license is a URL or expression.
/// </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 license stream, or null if the package license does not exist, or the license type is URL or expression.</returns>
Task<Stream> GetPackageLicenseStreamOrNullAsync(string id, NuGetVersion version, CancellationToken cancellationToken);
}
17 changes: 16 additions & 1 deletion src/BaGetter.Core/Entities/Package.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

namespace BaGetter.Core;

// See NuGetGallery's: https://github.com/NuGet/NuGetGallery/blob/master/src/NuGetGallery.Core/Entities/Package.cs
/// <remarks>
/// See: <see href="https://github.com/NuGet/NuGetGallery/blob/master/src/NuGetGallery.Core/Entities/Package.cs"/><br/>
/// Newer version: <see href="https://github.com/NuGet/NuGetGallery/blob/main/src/NuGet.Services.Entities/Package.cs"/>
/// </remarks>
public class Package
{
public int Key { get; set; }
Expand Down Expand Up @@ -34,10 +37,22 @@ public NuGetVersion Version
public string Description { get; set; }
public long Downloads { get; set; }
public bool HasReadme { get; set; }
/// <summary>
/// Indicates if the icon is embedded in the package.
/// </summary>
public bool HasEmbeddedIcon { get; set; }
/// <summary>
/// Indicates if the license is embedded in the package.
/// </summary>
public bool HasEmbeddedLicense { get; set; }
public bool IsPrerelease { get; set; }
public string ReleaseNotes { get; set; }
public string Language { get; set; }
/// <summary>
/// Indicates if the license format is Markdown.
/// </summary>
/// <value><c>true</c> if Markdown, <c>false</c> otherwise.</value>
public bool LicenseFormatIsMarkdown { get; set; }
public bool Listed { get; set; }
public string MinClientVersion { get; set; }
public DateTime Published { get; set; }
Expand Down
85 changes: 68 additions & 17 deletions src/BaGetter.Core/Extensions/PackageArchiveReaderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,39 @@ public static bool HasReadme(this PackageArchiveReader package)
public static bool HasEmbeddedIcon(this PackageArchiveReader package)
=> !string.IsNullOrEmpty(package.NuspecReader.GetIcon());

public async static Task<Stream> GetReadmeAsync(
this PackageArchiveReader package,
CancellationToken cancellationToken)
/// <summary>
/// Indicates if the package has an embedded license file.
/// </summary>
/// <returns><c>true</c> if the license file is embedded, otherwise <c>false</c>.</returns>
public static bool HasEmbeddedLicense(this PackageArchiveReader package)
{
var licenseMetadata = package.NuspecReader.GetLicenseMetadata();
if (licenseMetadata != null && licenseMetadata.Type == LicenseType.File)
{
return true;
}

return false;
}

/// <summary>
/// Indicates if the license file format is Markdown.
/// </summary>
/// <returns><c>true</c> if the license file format is Markdown, otherwise <c>false</c>.</returns>
public static bool IsLicenseFormatMarkdown(this PackageArchiveReader package)
{
var licenseMetadata = package.NuspecReader.GetLicenseMetadata();
if (licenseMetadata == null ||
licenseMetadata.Type != LicenseType.File ||
string.IsNullOrWhiteSpace(licenseMetadata.License))
{
return false;
}

return Path.GetFileName(licenseMetadata.License).EndsWith(".md", StringComparison.OrdinalIgnoreCase);
}

public static async Task<Stream> GetReadmeAsync(this PackageArchiveReader package, CancellationToken cancellationToken)
{
var readmePath = package.NuspecReader.GetReadme();
if (readmePath == null)
Expand All @@ -34,18 +64,28 @@ public async static Task<Stream> GetReadmeAsync(
return await package.GetStreamAsync(readmePath, cancellationToken);
}

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

public static Package GetPackageMetadata(this PackageArchiveReader packageReader)
public static async Task<Stream> GetLicenseAsync(this PackageArchiveReader package, CancellationToken cancellationToken)
{
var nuspec = packageReader.NuspecReader;
var licenseMetadata = package.NuspecReader.GetLicenseMetadata();
if (licenseMetadata.Type == LicenseType.File && licenseMetadata.License == null)
{
throw new InvalidOperationException("Package does not have a license file!");
}

var licensePath = PathUtility.StripLeadingDirectorySeparators(licenseMetadata.License);
return await package.GetStreamAsync(licensePath, cancellationToken);
}

public static Package GetPackageMetadata(this PackageArchiveReader package)
{
var nuspec = package.NuspecReader;

(var repositoryUri, var repositoryType) = GetRepositoryMetadata(nuspec);

Expand All @@ -55,10 +95,12 @@ public static Package GetPackageMetadata(this PackageArchiveReader packageReader
Version = nuspec.GetVersion(),
Authors = ParseAuthors(nuspec.GetAuthors()),
Description = nuspec.GetDescription(),
HasReadme = packageReader.HasReadme(),
HasEmbeddedIcon = packageReader.HasEmbeddedIcon(),
HasReadme = package.HasReadme(),
HasEmbeddedIcon = package.HasEmbeddedIcon(),
HasEmbeddedLicense = package.HasEmbeddedLicense(),
IsPrerelease = nuspec.GetVersion().IsPrerelease,
Language = nuspec.GetLanguage() ?? string.Empty,
LicenseFormatIsMarkdown = package.IsLicenseFormatMarkdown(),
ReleaseNotes = nuspec.GetReleaseNotes() ?? string.Empty,
Listed = true,
MinClientVersion = nuspec.GetMinClientVersion()?.ToNormalizedString() ?? string.Empty,
Expand All @@ -75,11 +117,11 @@ public static Package GetPackageMetadata(this PackageArchiveReader packageReader
Dependencies = GetDependencies(nuspec),
Tags = ParseTags(nuspec.GetTags()),
PackageTypes = GetPackageTypes(nuspec),
TargetFrameworks = GetTargetFrameworks(packageReader),
TargetFrameworks = GetTargetFrameworks(package),
};
}

// Based off https://github.com/NuGet/NuGetGallery/blob/master/src/NuGetGallery.Core/SemVerLevelKey.cs
/// <remarks>Based off: <see href="https://github.com/NuGet/NuGetGallery/blob/master/src/NuGetGallery.Core/SemVerLevelKey.cs"/></remarks>
private static SemVerLevel GetSemVerLevel(NuspecReader nuspec)
{
if (nuspec.GetVersion().IsSemVer2)
Expand All @@ -104,7 +146,10 @@ private static SemVerLevel GetSemVerLevel(NuspecReader nuspec)

private static Uri ParseUri(string uriString)
{
if (string.IsNullOrEmpty(uriString)) return null;
if (string.IsNullOrEmpty(uriString))
{
return null;
}

return new Uri(uriString);
}
Expand All @@ -113,14 +158,20 @@ private static Uri ParseUri(string uriString)

private static string[] ParseAuthors(string authors)
{
if (string.IsNullOrEmpty(authors)) return Array.Empty<string>();
if (string.IsNullOrEmpty(authors))
{
return Array.Empty<string>();
}

return authors.Split(Separator, StringSplitOptions.RemoveEmptyEntries);
}

private static string[] ParseTags(string tags)
{
if (string.IsNullOrEmpty(tags)) return Array.Empty<string>();
if (string.IsNullOrEmpty(tags))
{
return Array.Empty<string>();
}

return tags.Split(Separator, StringSplitOptions.RemoveEmptyEntries);
}
Expand Down Expand Up @@ -204,9 +255,9 @@ private static List<PackageType> GetPackageTypes(NuspecReader nuspec)
return packageTypes;
}

private static List<TargetFramework> GetTargetFrameworks(PackageArchiveReader packageReader)
private static List<TargetFramework> GetTargetFrameworks(PackageArchiveReader package)
{
var targetFrameworks = packageReader
var targetFrameworks = package
.GetSupportedFrameworks()
.Select(f => new TargetFramework
{
Expand Down
8 changes: 8 additions & 0 deletions src/BaGetter.Core/IUrlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,12 @@ public interface IUrlGenerator
/// <param name="id">The package's ID</param>
/// <param name="version">The package's version</param>
string GetPackageIconDownloadUrl(string id, NuGetVersion version);

/// <summary>
/// Get the URL to download a package's license.
/// </summary>
/// <param name="id">The package's ID</param>
/// <param name="version">The package's version</param>
/// <param name="licenseFormatIsMarkdown">Is the format of the license Markdown?</param>
string GetPackageLicenseDownloadUrl(string id, NuGetVersion version, bool licenseFormatIsMarkdown);
}
12 changes: 12 additions & 0 deletions src/BaGetter.Core/Indexing/PackageIndexingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public async Task<PackageIndexingResult> IndexAsync(Stream packageStream, Cancel
Stream nuspecStream;
Stream readmeStream;
Stream iconStream;
Stream licenseStream;

try
{
Expand Down Expand Up @@ -70,6 +71,16 @@ public async Task<PackageIndexingResult> IndexAsync(Stream packageStream, Cancel
{
iconStream = null;
}

if (package.HasEmbeddedLicense)
{
licenseStream = await packageReader.GetLicenseAsync(cancellationToken);
licenseStream = await licenseStream.AsTemporaryFileStreamAsync(cancellationToken);
}
else
{
licenseStream = null;
}
}
}
catch (Exception e)
Expand Down Expand Up @@ -108,6 +119,7 @@ await _storage.SavePackageContentAsync(
nuspecStream,
readmeStream,
iconStream,
licenseStream,
cancellationToken);
}
catch (Exception e)
Expand Down
4 changes: 3 additions & 1 deletion src/BaGetter.Core/Metadata/RegistrationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ private BaGetRegistrationIndexPageItem ToRegistrationIndexPageItem(Package packa
? _url.GetPackageIconDownloadUrl(package.Id, package.Version)
: package.IconUrlString,
Language = package.Language,
LicenseUrl = package.LicenseUrlString,
LicenseUrl = package.HasEmbeddedLicense
? _url.GetPackageLicenseDownloadUrl(package.Id, package.Version, package.LicenseFormatIsMarkdown)
: package.LicenseUrlString,
Listed = package.Listed,
MinClientVersion = package.MinClientVersion,
ReleaseNotes = package.ReleaseNotes,
Expand Down
7 changes: 5 additions & 2 deletions src/BaGetter.Core/Search/SearchResponseBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using BaGetter.Protocol.Models;
Expand All @@ -25,6 +25,9 @@ public SearchResponse BuildSearch(IReadOnlyList<PackageRegistration> packageRegi
var iconUrl = latest.HasEmbeddedIcon
? _url.GetPackageIconDownloadUrl(latest.Id, latest.Version)
: latest.IconUrlString;
var licenseUrl = latest.HasEmbeddedLicense
? _url.GetPackageLicenseDownloadUrl(latest.Id, latest.Version, latest.LicenseFormatIsMarkdown)
: latest.LicenseUrlString;

result.Add(new SearchResult
{
Expand All @@ -33,7 +36,7 @@ public SearchResponse BuildSearch(IReadOnlyList<PackageRegistration> packageRegi
Description = latest.Description,
Authors = latest.Authors,
IconUrl = iconUrl,
LicenseUrl = latest.LicenseUrlString,
LicenseUrl = licenseUrl,
ProjectUrl = latest.ProjectUrlString,
RegistrationIndexUrl = _url.GetRegistrationIndexUrl(latest.Id),
Summary = latest.Summary,
Expand Down
23 changes: 21 additions & 2 deletions src/BaGetter.Core/Storage/IPackageStorageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
namespace BaGetter.Core;

/// <summary>
/// Stores packages' content. Packages' state are stored by the
/// <see cref="IPackageDatabase"/>.
/// Stores packages' content.<br/>
/// Packages' state are stored by the <see cref="IPackageDatabase"/>.
/// </summary>
public interface IPackageStorageService
{
Expand All @@ -20,6 +20,7 @@ public interface IPackageStorageService
/// <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="licenseStream">The package's license stream, or null if none.</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task SavePackageContentAsync(
Expand All @@ -28,6 +29,7 @@ Task SavePackageContentAsync(
Stream nuspecStream,
Stream readmeStream,
Stream iconStream,
Stream licenseStream,
CancellationToken cancellationToken);

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

/// <summary>
/// Retrieve a package's icon stream if the icon is an embedded file.
/// </summary>
/// <param name="id">The package's id.</param>
/// <param name="version">The package's version.</param>
/// <param name="cancellationToken"></param>
/// <returns>The package's icon stream.</returns>
Task<Stream> GetIconStreamAsync(string id, NuGetVersion version, CancellationToken cancellationToken);

/// <summary>
/// Retrieve a package's license stream if the license is an embedded file.
/// </summary>
/// <param name="id">The package's id.</param>
/// <param name="version">The package's version.</param>
/// <param name="licenseFormatIsMarkdown">Is the format of the license Markdown?</param>
/// <param name="cancellationToken"></param>
/// <returns>The package's license stream.</returns>
Task<Stream> GetLicenseStreamAsync(string id, NuGetVersion version, bool licenseFormatIsMarkdown, CancellationToken cancellationToken);

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