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

Async Improvements #32

Merged
merged 7 commits into from
Jan 22, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions MetaBrainz.MusicBrainz.CoverArt.sln.DotSettings
Expand Up @@ -61,5 +61,6 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Brainz/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=coverartarchive/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=libcoverart/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=MBID/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
86 changes: 55 additions & 31 deletions MetaBrainz.MusicBrainz.CoverArt/CoverArt.cs
Expand Up @@ -241,14 +241,15 @@ public CoverArt(string application, string version, string contact)
/// </li></ul>
/// </exception>
public CoverArtImage FetchBack(Guid mbid, CoverArtImageSize size = CoverArtImageSize.Original)
=> CoverArt.ResultOf(this.FetchImageAsync("release", mbid, "back", size));
=> CoverArt.ResultOf(this.FetchBackAsync(mbid, size));

/// <summary>Fetch the main "back" image for the specified release.</summary>
/// <param name="mbid">The MusicBrainz release ID for which the image is requested.</param>
/// <param name="size">
/// The requested image size; <see cref="CoverArtImageSize.Original"/> can be any content type, but the thumbnails are always
/// JPEG.
/// </param>
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
/// <returns>An asynchronous operation returning the requested image data.</returns>
/// <exception cref="WebException">
/// When something went wrong with the request.
Expand All @@ -260,8 +261,9 @@ public CoverArtImage FetchBack(Guid mbid, CoverArtImageSize size = CoverArtImage
/// 503 (<see cref="HttpStatusCode.ServiceUnavailable"/>) when the server is unavailable, or rate limiting is in effect.
/// </li></ul>
/// </exception>
public Task<CoverArtImage> FetchBackAsync(Guid mbid, CoverArtImageSize size = CoverArtImageSize.Original)
=> this.FetchImageAsync("release", mbid, "back", size);
public Task<CoverArtImage> FetchBackAsync(Guid mbid, CoverArtImageSize size = CoverArtImageSize.Original,
CancellationToken cancellationToken = new())
=> this.FetchImageAsync("release", mbid, "back", size, cancellationToken);

/// <summary>Fetch the main "front" image for the specified release, in the specified size.</summary>
/// <param name="mbid">The MusicBrainz release ID for which the image is requested.</param>
Expand All @@ -275,20 +277,21 @@ public Task<CoverArtImage> FetchBackAsync(Guid mbid, CoverArtImageSize size = Co
/// More details can be found in the exception's <see cref="WebException.Response"/> property.<br/>
/// Possible status codes for the response are:
/// <ul><li>
/// 404 (<see cref="HttpStatusCode.NotFound"/>) when the release does not exist (or has no "fromt" image set);
/// 404 (<see cref="HttpStatusCode.NotFound"/>) when the release does not exist (or has no "front" image set);
/// </li><li>
/// 503 (<see cref="HttpStatusCode.ServiceUnavailable"/>) when the server is unavailable, or rate limiting is in effect.
/// </li></ul>
/// </exception>
public CoverArtImage FetchFront(Guid mbid, CoverArtImageSize size = CoverArtImageSize.Original)
=> CoverArt.ResultOf(this.FetchImageAsync("release", mbid, "front", size));
=> CoverArt.ResultOf(this.FetchFrontAsync(mbid, size));

/// <summary>Fetch the main "front" image for the specified release, in the specified size.</summary>
/// <param name="mbid">The MusicBrainz release ID for which the image is requested.</param>
/// <param name="size">
/// The requested image size; <see cref="CoverArtImageSize.Original"/> can be any content type, but the thumbnails are always
/// JPEG.
/// </param>
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
/// <returns>An asynchronous operation returning the requested image data.</returns>
/// <exception cref="WebException">
/// When something went wrong with the request.
Expand All @@ -300,8 +303,9 @@ public CoverArtImage FetchFront(Guid mbid, CoverArtImageSize size = CoverArtImag
/// 503 (<see cref="HttpStatusCode.ServiceUnavailable"/>) when the server is unavailable, or rate limiting is in effect.
/// </li></ul>
/// </exception>
public Task<CoverArtImage> FetchFrontAsync(Guid mbid, CoverArtImageSize size = CoverArtImageSize.Original)
=> this.FetchImageAsync("release", mbid, "front", size);
public Task<CoverArtImage> FetchFrontAsync(Guid mbid, CoverArtImageSize size = CoverArtImageSize.Original,
CancellationToken cancellationToken = new())
=> this.FetchImageAsync("release", mbid, "front", size, cancellationToken);

/// <summary>Fetch the main "front" image for the specified release group, in the specified size.</summary>
/// <param name="mbid">The MusicBrainz release group ID for which the image is requested.</param>
Expand All @@ -321,14 +325,15 @@ public Task<CoverArtImage> FetchFrontAsync(Guid mbid, CoverArtImageSize size = C
/// </li></ul>
/// </exception>
public CoverArtImage FetchGroupFront(Guid mbid, CoverArtImageSize size = CoverArtImageSize.Original)
=> CoverArt.ResultOf(this.FetchImageAsync("release-group", mbid, "front", size));
=> CoverArt.ResultOf(this.FetchGroupFrontAsync(mbid, size));

/// <summary>Fetch the main "front" image for the specified release group, in the specified size.</summary>
/// <param name="mbid">The MusicBrainz release group ID for which the image is requested.</param>
/// <param name="size">
/// The requested image size; <see cref="CoverArtImageSize.Original"/> can be any content type, but the thumbnails are always
/// JPEG.
/// </param>
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
/// <returns>An asynchronous operation returning the requested image data.</returns>
/// <exception cref="WebException">
/// When something went wrong with the request.
Expand All @@ -340,8 +345,9 @@ public CoverArtImage FetchGroupFront(Guid mbid, CoverArtImageSize size = CoverAr
/// 503 (<see cref="HttpStatusCode.ServiceUnavailable"/>) when the server is unavailable, or rate limiting is in effect.
/// </li></ul>
/// </exception>
public Task<CoverArtImage> FetchGroupFrontAsync(Guid mbid, CoverArtImageSize size = CoverArtImageSize.Original)
=> this.FetchImageAsync("release-group", mbid, "front", size);
public Task<CoverArtImage> FetchGroupFrontAsync(Guid mbid, CoverArtImageSize size = CoverArtImageSize.Original,
CancellationToken cancellationToken = new())
=> this.FetchImageAsync("release-group", mbid, "front", size, cancellationToken);

/// <summary>Fetch information about the cover art associated with the specified MusicBrainz release group (if any).</summary>
/// <param name="mbid">The MusicBrainz release group ID for which cover art information is requested.</param>
Expand All @@ -358,10 +364,11 @@ public Task<CoverArtImage> FetchGroupFrontAsync(Guid mbid, CoverArtImageSize siz
/// 503 (<see cref="HttpStatusCode.ServiceUnavailable"/>) when the server is unavailable, or rate limiting is in effect.
/// </li></ul>
/// </exception>
public IRelease FetchGroupRelease(Guid mbid) => CoverArt.ResultOf(this.FetchReleaseAsync("release-group", mbid));
public IRelease FetchGroupRelease(Guid mbid) => CoverArt.ResultOf(this.FetchGroupReleaseAsync(mbid));

/// <summary>Fetch information about the cover art associated with the specified MusicBrainz release group (if any).</summary>
/// <param name="mbid">The MusicBrainz release group ID for which cover art information is requested.</param>
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
/// <returns>
/// An asynchronous operation returning a <see cref="Release"/> object containing information about the cover art for the
/// release group's main release.
Expand All @@ -376,7 +383,8 @@ public Task<CoverArtImage> FetchGroupFrontAsync(Guid mbid, CoverArtImageSize siz
/// 503 (<see cref="HttpStatusCode.ServiceUnavailable"/>) when the server is unavailable, or rate limiting is in effect.
/// </li></ul>
/// </exception>
public Task<IRelease> FetchGroupReleaseAsync(Guid mbid) => this.FetchReleaseAsync("release-group", mbid);
public Task<IRelease> FetchGroupReleaseAsync(Guid mbid, CancellationToken cancellationToken = new())
=> this.FetchReleaseAsync("release-group", mbid, cancellationToken);

/// <summary>Fetch the specified image for the specified release, in the specified size.</summary>
/// <param name="mbid">The MusicBrainz release ID for which the image is requested.</param>
Expand All @@ -399,7 +407,7 @@ public Task<CoverArtImage> FetchGroupFrontAsync(Guid mbid, CoverArtImageSize siz
/// </li></ul>
/// </exception>
public CoverArtImage FetchImage(Guid mbid, string id, CoverArtImageSize size = CoverArtImageSize.Original)
=> CoverArt.ResultOf(this.FetchImageAsync("release", mbid, id, size));
=> CoverArt.ResultOf(this.FetchImageAsync(mbid, id, size));

/// <summary>Fetch the specified image for the specified release, in the specified size.</summary>
/// <param name="mbid">The MusicBrainz release ID for which the image is requested.</param>
Expand All @@ -410,6 +418,7 @@ public CoverArtImage FetchImage(Guid mbid, string id, CoverArtImageSize size = C
/// The requested image size; <see cref="CoverArtImageSize.Original"/> can be any content type, but the thumbnails are always
/// JPEG.
/// </param>
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
/// <returns>An asynchronous operation returning the requested image data.</returns>
/// <exception cref="WebException">
/// When something went wrong with the request.
Expand All @@ -421,8 +430,9 @@ public CoverArtImage FetchImage(Guid mbid, string id, CoverArtImageSize size = C
/// 503 (<see cref="HttpStatusCode.ServiceUnavailable"/>) when the server is unavailable, or rate limiting is in effect.
/// </li></ul>
/// </exception>
public Task<CoverArtImage> FetchImageAsync(Guid mbid, string id, CoverArtImageSize size = CoverArtImageSize.Original)
=> this.FetchImageAsync("release", mbid, id, size);
public Task<CoverArtImage> FetchImageAsync(Guid mbid, string id, CoverArtImageSize size = CoverArtImageSize.Original,
CancellationToken cancellationToken = new())
=> this.FetchImageAsync("release", mbid, id, size, cancellationToken);

/// <summary>Fetch information about the cover art associated with the specified MusicBrainz release (if any).</summary>
/// <param name="mbid">The MusicBrainz release ID for which cover art information is requested.</param>
Expand All @@ -437,10 +447,11 @@ public Task<CoverArtImage> FetchImageAsync(Guid mbid, string id, CoverArtImageSi
/// 503 (<see cref="HttpStatusCode.ServiceUnavailable"/>) when the server is unavailable, or rate limiting is in effect.
/// </li></ul>
/// </exception>
public IRelease FetchRelease(Guid mbid) => CoverArt.ResultOf(this.FetchReleaseAsync("release", mbid));
public IRelease FetchRelease(Guid mbid) => CoverArt.ResultOf(this.FetchReleaseAsync(mbid));

/// <summary>Fetch information about the cover art associated with the specified MusicBrainz release (if any).</summary>
/// <param name="mbid">The MusicBrainz release ID for which cover art information is requested.</param>
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
/// <returns>
/// An asynchronous operation returning a <see cref="Release"/> object containing information about the cover art for the
/// release.
Expand All @@ -455,7 +466,8 @@ public Task<CoverArtImage> FetchImageAsync(Guid mbid, string id, CoverArtImageSi
/// 503 (<see cref="HttpStatusCode.ServiceUnavailable"/>) when the server is unavailable, or rate limiting is in effect.
/// </li></ul>
/// </exception>
public Task<IRelease> FetchReleaseAsync(Guid mbid) => this.FetchReleaseAsync("release", mbid);
public Task<IRelease> FetchReleaseAsync(Guid mbid, CancellationToken cancellationToken = new())
=> this.FetchReleaseAsync("release", mbid, cancellationToken);

#endregion

Expand Down Expand Up @@ -537,11 +549,11 @@ public Task<CoverArtImage> FetchImageAsync(Guid mbid, string id, CoverArtImageSi

#region Basic Request Execution

private async Task<HttpResponseMessage> PerformRequestAsync(string address) {
private async Task<HttpResponseMessage> PerformRequestAsync(string address, CancellationToken cancellationToken) {
Debug.Print($"[{DateTime.UtcNow}] CAA REQUEST: GET {this.BaseUri}{address}");
var client = this.Client;
var request = new HttpRequestMessage(HttpMethod.Get, address);
var response = await client.SendAsync(request);
var response = await client.SendAsync(request, cancellationToken).ConfigureAwait(false);
Debug.Print($"[{DateTime.UtcNow}] => RESPONSE: {(int) response.StatusCode}/{response.StatusCode} '{response.ReasonPhrase}' " +
$"(v{response.Version})");
Debug.Print($"[{DateTime.UtcNow}] => HEADERS: {CoverArt.FormatMultiLine(response.Headers.ToString())}");
Expand All @@ -550,44 +562,55 @@ public Task<CoverArtImage> FetchImageAsync(Guid mbid, string id, CoverArtImageSi
return response;
}

private async Task<CoverArtImage> FetchImageAsync(string entity, Guid mbid, string id, CoverArtImageSize size) {
private async Task<CoverArtImage> FetchImageAsync(string entity, Guid mbid, string id, CoverArtImageSize size,
CancellationToken cancellationToken) {
var suffix = string.Empty;
if (size != CoverArtImageSize.Original) {
suffix = "-" + ((int) size).ToString(CultureInfo.InvariantCulture);
}
var address= $"{entity}/{mbid:D}/{id}{suffix}";
using var response = await this.PerformRequestAsync(address).ConfigureAwait(false);
using var response = await this.PerformRequestAsync(address, cancellationToken).ConfigureAwait(false);
var contentLength = response.Content.Headers.ContentLength ?? 0;
if (contentLength > CoverArt.MaxImageSize) {
throw new ArgumentException($"The requested image is too large ({contentLength} > {CoverArt.MaxImageSize}).");
}
#if NET || NETSTANDARD2_1_OR_GREATER
var stream = await response.Content.ReadAsStreamAsync();
#if NET
var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
await using var _ = stream.ConfigureAwait(false);
#elif NETSTANDARD2_1_OR_GREATER
var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
await using var _ = stream.ConfigureAwait(false);
#else
using var stream = await response.Content.ReadAsStreamAsync();
using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
#endif
if (stream == null) {
throw new WebException("No data received.", WebExceptionStatus.ReceiveFailure);
}
var data = new MemoryStream();
try {
await stream.CopyToAsync(data).ConfigureAwait(false);
await stream.CopyToAsync(data, 64 * 1024, cancellationToken).ConfigureAwait(false);
}
catch {
#if NET || NETSTANDARD2_1_OR_GREATER
await data.DisposeAsync().ConfigureAwait(false);
#else
data.Dispose();
#endif
throw;
}
return new CoverArtImage(id, size, response.Content?.Headers?.ContentType?.MediaType, data);
}

private async Task<IRelease> FetchReleaseAsync(string entity, Guid mbid) {
using var response = await this.PerformRequestAsync($"{entity}/{mbid:D}").ConfigureAwait(false);
#if NET || NETSTANDARD2_1_OR_GREATER
var stream = await response.Content.ReadAsStreamAsync();
private async Task<IRelease> FetchReleaseAsync(string entity, Guid mbid, CancellationToken cancellationToken) {
using var response = await this.PerformRequestAsync($"{entity}/{mbid:D}", cancellationToken).ConfigureAwait(false);
#if NET
var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
await using var _ = stream.ConfigureAwait(false);
#elif NETSTANDARD2_1_OR_GREATER
var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
await using var _ = stream.ConfigureAwait(false);
#else
using var stream = await response.Content.ReadAsStreamAsync();
using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
#endif
if (stream == null) {
throw new WebException("No data received.", WebExceptionStatus.ReceiveFailure);
Expand All @@ -599,7 +622,8 @@ public Task<CoverArtImage> FetchImageAsync(Guid mbid, string id, CoverArtImageSi
IRelease? release;
#if !DEBUG
if (characterSet == "utf-8") { // Directly use the stream
release = await JsonUtils.DeserializeAsync<Release>(stream, CoverArt.JsonReaderOptions);
var jsonTask = JsonUtils.DeserializeAsync<Release>(stream, CoverArt.JsonReaderOptions, cancellationToken);
release = await jsonTask.ConfigureAwait(false);
return release ?? throw new JsonException("Received a null release.");
}
#endif
Expand Down
2 changes: 1 addition & 1 deletion MetaBrainz.MusicBrainz.CoverArt/CoverArtImage.cs
Expand Up @@ -36,7 +36,7 @@ public sealed class CoverArtImage : IDisposable {
/// <exception cref="ArgumentException">
/// When the image data is not valid (or not supported by the <see cref="System.Drawing.Image"/> class).
/// </exception>
/// <remarks>This method only </remarks>
/// <exception cref="PlatformNotSupportedException">When not running on Windows.</exception>
#if NET
[SupportedOSPlatform("windows")]
#endif
Expand Down
Expand Up @@ -3,11 +3,14 @@

<PropertyGroup>
<Title>CoverArt Archive Web Service Client Library</Title>
<Description>This package provides classes for accessing the CoverArt Archive (CAA), enabling the retrieval of cover art for music releases based on their MusicBrainz ID.</Description>
<PackageCopyrightYears>2016-2021</PackageCopyrightYears>
<Description>
This package provides classes for accessing the CoverArt Archive (CAA), enabling the retrieval of cover art for music releases
based on their MusicBrainz ID.
</Description>
<PackageCopyrightYears>2016-2022</PackageCopyrightYears>
<PackageRepositoryName>MetaBrainz.MusicBrainz.CoverArt</PackageRepositoryName>
<PackageTags>MusicBrainz album cover art archive CAA libcoverart</PackageTags>
<Version>4.0.1-pre</Version>
<Version>5.0.0-pre</Version>
</PropertyGroup>

<ItemGroup>
Expand Down