diff --git a/README.md b/README.md index 78f3d79..94c2bb1 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Implementation of the [MusicBrainz](https://musicbrainz.org/) API version 2 for - First class MusicBrainz entities `Artist`, `Label`, `ReleaseGroup`, `Release` and `Recording` supporting asynchronous `Get` (lookup by MBID), `Search` and `Browse`. - Advanced `Search` using Lucene query syntax (see [search documentation](https://musicbrainz.org/doc/MusicBrainz_API/Search) for supported fields). -- Support for sub-queries in lookup requests (see MusicBrainz [documentation](https://musicbrainz.org/doc/MusicBrainz_API#inc=_arguments_which_affect_subqueries) and [wiki](https://wiki.musicbrainz.org/User:Nikki/ws/2) for a list of supported `inc` parameters). +- Support for sub-queries in lookup requests (see MusicBrainz [documentation](https://musicbrainz.org/doc/MusicBrainz_API#Subqueries) and [wiki](https://wiki.musicbrainz.org/User:Nikki/ws/2) for a list of supported `inc` parameters). - Limited support for entity relationships. More information about the MusicBrainz API can be found in the [documentation](https://musicbrainz.org/doc/MusicBrainz_API). diff --git a/assets/readme.md b/assets/readme.md index 81e19a8..2493c66 100644 --- a/assets/readme.md +++ b/assets/readme.md @@ -6,7 +6,7 @@ Implementation of the [MusicBrainz](https://musicbrainz.org/) API version 2 for - First class MusicBrainz entities `Artist`, `Label`, `ReleaseGroup`, `Release` and `Recording` supporting asynchronous `Get` (lookup by MBID), `Search` and `Browse`. - Advanced `Search` using Lucene query syntax (see [search documentation](https://musicbrainz.org/doc/MusicBrainz_API/Search) for supported fields). -- Support for sub-queries in lookup requests (see MusicBrainz [documentation](https://musicbrainz.org/doc/MusicBrainz_API#inc=_arguments_which_affect_subqueries) and [wiki](https://wiki.musicbrainz.org/User:Nikki/ws/2) for a list of supported `inc` parameters). +- Support for sub-queries in lookup requests (see MusicBrainz [documentation](https://musicbrainz.org/doc/MusicBrainz_API#Subqueries) and [wiki](https://wiki.musicbrainz.org/User:Nikki/ws/2) for a list of supported `inc` parameters). - Limited support for entity relationships. More information about the MusicBrainz API can be found [here](https://musicbrainz.org/doc/MusicBrainz_API). diff --git a/src/Hqub.MusicBrainz.Client/Example5.cs b/src/Hqub.MusicBrainz.Client/Example5.cs index c6869ca..a92b23d 100644 --- a/src/Hqub.MusicBrainz.Client/Example5.cs +++ b/src/Hqub.MusicBrainz.Client/Example5.cs @@ -36,7 +36,7 @@ private static async Task Browse(MusicBrainzClient client, string name) Console.WriteLine(); // Show official albums. - foreach (var item in groups.Items.Where(g => IsOfficial(g)).OrderBy(g => g.FirstReleaseDate)) + foreach (var item in groups.Items.Where(IsOfficial).OrderBy(g => g.FirstReleaseDate)) { Console.WriteLine(" {0} - {1} {2} {3}", item.FirstReleaseDate.ToShortDate(), item.Id, GetRating(item.Rating, 10), item.Title); @@ -47,7 +47,7 @@ private static async Task Browse(MusicBrainzClient client, string name) Console.WriteLine(); // Show compilations. - foreach (var item in groups.Items.Where(g => IsCompilation(g)).OrderBy(g => g.FirstReleaseDate)) + foreach (var item in groups.Items.Where(IsCompilation).OrderBy(g => g.FirstReleaseDate)) { Console.WriteLine(" {0} - {1} {2} {3}", item.FirstReleaseDate.ToShortDate(), item.Id, GetRating(item.Rating, 10), item.Title); diff --git a/src/Hqub.MusicBrainz.Client/Example7.cs b/src/Hqub.MusicBrainz.Client/Example7.cs index aa3d30d..3e7a1cc 100644 --- a/src/Hqub.MusicBrainz.Client/Example7.cs +++ b/src/Hqub.MusicBrainz.Client/Example7.cs @@ -8,7 +8,7 @@ namespace Hqub.MusicBrainz.Client /// /// Search for a label using client.Labels.SearchAsync() and lookup label details - /// like releases and related urls using clients.Label.GetAsync(). + /// like releases and related urls using client.Labels.GetAsync(). /// public class Example7 { diff --git a/src/Hqub.MusicBrainz.Client/FetchTestData.cs b/src/Hqub.MusicBrainz.Client/FetchTestData.cs index 031f4ce..f19c570 100644 --- a/src/Hqub.MusicBrainz.Client/FetchTestData.cs +++ b/src/Hqub.MusicBrainz.Client/FetchTestData.cs @@ -43,7 +43,6 @@ public static async Task Download(bool overwrite = false) foreach (var item in Data) { await Get(client, item.Key, item.Value, overwrite); - } Console.Write("Done. Press any key to quit."); diff --git a/src/Hqub.MusicBrainz.Client/Program.cs b/src/Hqub.MusicBrainz.Client/Program.cs index 5789d5a..c7fa7c1 100644 --- a/src/Hqub.MusicBrainz.Client/Program.cs +++ b/src/Hqub.MusicBrainz.Client/Program.cs @@ -14,6 +14,10 @@ private static async Task Main(string[] args) { await RunExamples(); } + catch (WebServiceException e) + { + Error(e); + } catch (Exception e) { Console.WriteLine(e.Message); @@ -66,5 +70,23 @@ private static void Header(string title) Console.ForegroundColor = color; } + + private static void Error(WebServiceException e) + { + var color = Console.ForegroundColor; + + Console.ForegroundColor = ConsoleColor.DarkRed; + + Console.WriteLine(); + Console.WriteLine("MusicBrainz web service reported an error"); + + Console.ForegroundColor = color; + + Console.WriteLine(" Requested URL: " + e.Uri); + Console.WriteLine(" Status Code: " + e.StatusCode); + Console.WriteLine(" Message: " + e.Message); + Console.WriteLine(); + + } } } diff --git a/src/Hqub.MusicBrainz.Tests/Hqub.MusicBrainz.Tests.csproj b/src/Hqub.MusicBrainz.Tests/Hqub.MusicBrainz.Tests.csproj index 9cc78dd..0ab5f16 100644 --- a/src/Hqub.MusicBrainz.Tests/Hqub.MusicBrainz.Tests.csproj +++ b/src/Hqub.MusicBrainz.Tests/Hqub.MusicBrainz.Tests.csproj @@ -50,8 +50,8 @@ - - + + diff --git a/src/Hqub.MusicBrainz.Tests/ReleaseGetTests.cs b/src/Hqub.MusicBrainz.Tests/ReleaseGetTests.cs index 08da7ba..a206427 100644 --- a/src/Hqub.MusicBrainz.Tests/ReleaseGetTests.cs +++ b/src/Hqub.MusicBrainz.Tests/ReleaseGetTests.cs @@ -36,6 +36,7 @@ public void TestReleaseElements() Assert.That(release.Date, Is.EqualTo("2012-06-11")); Assert.That(release.Country, Is.EqualTo("US")); Assert.That(release.Barcode, Is.EqualTo("809236126221")); + Assert.That(release.Packaging, Is.EqualTo("Cardboard/Paper Sleeve")); } [Test] diff --git a/src/Hqub.MusicBrainz.Tests/UrlBuilderTests.cs b/src/Hqub.MusicBrainz.Tests/UrlBuilderTests.cs new file mode 100644 index 0000000..99632d1 --- /dev/null +++ b/src/Hqub.MusicBrainz.Tests/UrlBuilderTests.cs @@ -0,0 +1,77 @@ +namespace Hqub.MusicBrainz.Tests +{ + using NUnit.Framework; + + public class UrlBuilder + { + private MusicBrainzClient client; + + [OneTimeSetUp] + public void Init() + { + client = new MusicBrainzClient(); + } + + [Test] + public void TestLookupRequest() + { + var request = client.Releases.Get("00000000-0000-0000-0000-000000000000"); + + const string expected = "release/00000000-0000-0000-0000-000000000000?fmt=json"; + + Assert.That(request.ToString(), Is.EqualTo(expected)); + + request.Include("artists", "tags"); + + Assert.That(request.ToString(), Is.EqualTo(expected + "&inc=artists+tags")); + } + + [Test] + public void TestSearchRequest() + { + var request = client.Releases.Search("search-string"); + + const string expected = "release?query=search-string&fmt=json"; + + Assert.That(request.ToString(), Is.EqualTo(expected)); + + request.Limit(10); + + Assert.That(request.ToString(), Is.EqualTo(expected + "&limit=10")); + + request.Offset(20); + + Assert.That(request.ToString(), Is.EqualTo(expected + "&limit=10&offset=20")); + } + + [Test] + public void TestBrowseRequest() + { + var request = client.Releases.Browse("label", "00000000-0000-0000-0000-000000000000"); + + const string expected = "release?label=00000000-0000-0000-0000-000000000000&fmt=json"; + + Assert.That(request.ToString(), Is.EqualTo(expected)); + + request.Limit(10); + + Assert.That(request.ToString(), Is.EqualTo(expected + "&limit=10")); + + request.Offset(20); + + Assert.That(request.ToString(), Is.EqualTo(expected + "&limit=10&offset=20")); + + request.Include("artist-credits", "tags"); + + Assert.That(request.ToString(), Is.EqualTo(expected + "&limit=10&offset=20&inc=artist-credits+tags")); + + request.Type("album|ep"); + + Assert.That(request.ToString(), Is.EqualTo(expected + "&limit=10&offset=20&inc=artist-credits+tags&type=album|ep")); + + request.Status("official"); + + Assert.That(request.ToString(), Is.EqualTo(expected + "&limit=10&offset=20&inc=artist-credits+tags&type=album|ep&status=official")); + } + } +} diff --git a/src/Hqub.MusicBrainz/Entities/Release.cs b/src/Hqub.MusicBrainz/Entities/Release.cs index f24b200..754bdde 100644 --- a/src/Hqub.MusicBrainz/Entities/Release.cs +++ b/src/Hqub.MusicBrainz/Entities/Release.cs @@ -68,6 +68,12 @@ public class Release : IEntity [DataMember(Name = "barcode")] public string Barcode { get; set; } + /// + /// Gets or sets the packaging. + /// + [DataMember(Name = "packaging")] + public string Packaging { get; set; } + /// /// Gets or sets the release-group. /// diff --git a/src/Hqub.MusicBrainz/Hqub.MusicBrainz.csproj b/src/Hqub.MusicBrainz/Hqub.MusicBrainz.csproj index 80d06eb..5fdc96e 100644 --- a/src/Hqub.MusicBrainz/Hqub.MusicBrainz.csproj +++ b/src/Hqub.MusicBrainz/Hqub.MusicBrainz.csproj @@ -13,10 +13,10 @@ Boris Glebov, Christian Woltering https://github.com/avatar29A/MusicBrainz https://github.com/avatar29A/MusicBrainz - 3.2.0.0 - 3.2.0.0 + 4.0.0.0 + 4.0.0.0 music musicbrainz brainz - 3.2.0 + 4.0.0 Hqub.MusicBrainz Hqub.MusicBrainz Debug;Release @@ -27,23 +27,18 @@ MIT git - Hqub.MusicBrainz version 3 breaking changes: + Hqub.MusicBrainz version 4.0 breaking changes: - * Remove the obsolete static API: fix by using the MusicBrainzClient class. - * Remove the ".API" suffix from assembly and namespaces: fix by removing the ".API" suffix in your using statements. + * Consistently return QueryResult instead of specialized collection types from search and browse requests. + * Removal of Hqub.MusicBrainz.Entities.Collections namespace. + * Removal of Area.IsoCodes property (use Iso1Codes or Iso2Codes instead). - Version 3.1 changes: + Other changes in version 4.0: - * Remove net6.0 target and add net8.0 - * Remove obsolete serialization support from WebServiceException - * Improve cache interface (add Contains(request) method) - * Improve fluent API requests (add ToString() method returning the URL path of the request) - - Version 3.2 changes: - - * Add support for label lookup, search and browse - * Add release events info to releases - * Improve documentation + * Introduce IEntity as base interface for all entity classes supported by services. + * Add support for Area relationships. + * Add support for Area lookup to MusicBrainz client. + * Add missing Release.Packaging property. diff --git a/src/Hqub.MusicBrainz/MusicBrainzClient.cs b/src/Hqub.MusicBrainz/MusicBrainzClient.cs index 55b8576..3af77c7 100644 --- a/src/Hqub.MusicBrainz/MusicBrainzClient.cs +++ b/src/Hqub.MusicBrainz/MusicBrainzClient.cs @@ -139,7 +139,7 @@ class ResponseError { /// The error message. [DataMember(Name = "error")] - public string Message; + public string Message { get; set; } } internal async Task GetAsync(string url, CancellationToken ct = default) diff --git a/src/Hqub.MusicBrainz/Resources/Constants.Designer.cs b/src/Hqub.MusicBrainz/Resources/Constants.Designer.cs index f9b7756..4857e3d 100644 --- a/src/Hqub.MusicBrainz/Resources/Constants.Designer.cs +++ b/src/Hqub.MusicBrainz/Resources/Constants.Designer.cs @@ -70,7 +70,7 @@ internal static string ArtistQueryParams { } /// - /// Looks up a localized string similar to +official+promotion+bootleg+pseudo-release+. + /// Looks up a localized string similar to +official+promotion+bootleg+pseudo-release+withdrawn+cancelled+. /// internal static string BrowseStatus { get { @@ -79,7 +79,7 @@ internal static string BrowseStatus { } /// - /// Looks up a localized string similar to +nat+album+single+ep+compilation+soundtrack+spokenword+interview+audiobook+live+remix+other+. + /// Looks up a localized string similar to +album+single+ep+broadcast+audiobook+compilation+demo+dj-mix+soundtrack+spokenword+interview+live+remix+other+. /// internal static string BrowseType { get { diff --git a/src/Hqub.MusicBrainz/Resources/Constants.resx b/src/Hqub.MusicBrainz/Resources/Constants.resx index 0c0bb24..28d2df0 100644 --- a/src/Hqub.MusicBrainz/Resources/Constants.resx +++ b/src/Hqub.MusicBrainz/Resources/Constants.resx @@ -121,10 +121,10 @@ -alias-primary_alias-area-arid-artist-artistaccent-alias-begin-beginarea-comment-country-end-endarea-ended-gender-ipi-isni-sortname-tag-type- - +official+promotion+bootleg+pseudo-release+ + +official+promotion+bootleg+pseudo-release+withdrawn+cancelled+ - +nat+album+single+ep+compilation+soundtrack+spokenword+interview+audiobook+live+remix+other+ + +album+single+ep+broadcast+audiobook+compilation+demo+dj-mix+soundtrack+spokenword+interview+live+remix+other+ -alias-area-begin-code-comment-country-end-ended-ipi-isni-label-labelaccent-laid-release_count-sortname-tag-type- diff --git a/src/Hqub.MusicBrainz/UrlBuilder.cs b/src/Hqub.MusicBrainz/UrlBuilder.cs index 28f0681..0944d46 100644 --- a/src/Hqub.MusicBrainz/UrlBuilder.cs +++ b/src/Hqub.MusicBrainz/UrlBuilder.cs @@ -10,9 +10,12 @@ namespace Hqub.MusicBrainz /// internal class UrlBuilder { - private const string LookupTemplate = "{0}/{1}/?inc={2}&fmt=json"; - private const string BrowseTemplate = "{0}?{1}={2}&limit={3}&offset={4}&inc={5}&fmt=json"; - private const string SearchTemplate = "{0}?query={1}&limit={2}&offset={3}&fmt=json"; + private const string LookupTemplate = "{0}/{1}?fmt=json"; + private const string BrowseTemplate = "{0}?{1}={2}&fmt=json"; + private const string SearchTemplate = "{0}?query={1}&fmt=json"; + + private const int DEFAULT_LIMIT = 25; + private const int DEFAULT_OFFSET = 0; private readonly bool validate; @@ -26,15 +29,14 @@ public UrlBuilder(bool clientSideValidation) /// public string CreateLookupUrl(string entity, string mbid, params string[] inc) { - return CreateLookupUrl(entity, mbid, string.Join("+", inc)); - } + var url = string.Format(LookupTemplate, entity, mbid); - /// - /// Creates a web service lookup template. - /// - private string CreateLookupUrl(string entity, string mbid, string inc) - { - return string.Format(LookupTemplate, entity, mbid, inc); + if (inc != null && inc.Length > 0) + { + url += "&inc=" + string.Join("+", inc); + } + + return url; } /// @@ -42,7 +44,7 @@ private string CreateLookupUrl(string entity, string mbid, string inc) /// public string CreateBrowseUrl(string entity, string relatedEntity, string mbid, int limit, int offset, params string[] inc) { - return CreateBrowseUrl(entity, relatedEntity, mbid, limit, offset, string.Join("+", inc)); + return CreateBrowseUrl(entity, relatedEntity, mbid, null, null, limit, offset, inc); } /// @@ -51,39 +53,33 @@ public string CreateBrowseUrl(string entity, string relatedEntity, string mbid, public string CreateBrowseUrl(string entity, string relatedEntity, string mbid, string type, string status, int limit, int offset, params string[] inc) { - var url = CreateBrowseUrl(entity, relatedEntity, mbid, limit, offset, string.Join("+", inc)); + var url = string.Format(BrowseTemplate, entity, relatedEntity, mbid); - if (validate && !ValidateBrowseParam(Resources.Constants.BrowseStatus, status)) - { - throw new ArgumentException(string.Format(Resources.Messages.InvalidQueryValue, status, "status")); - } - - if (validate && !ValidateBrowseParam(Resources.Constants.BrowseType, type)) - { - throw new ArgumentException(string.Format(Resources.Messages.InvalidQueryValue, type, "type")); - } + url = AddOptionalParams(url, limit, offset, inc); if (!string.IsNullOrEmpty(type)) { + if (validate && !ValidateBrowseParam(Resources.Constants.BrowseType, type)) + { + throw new ArgumentException(string.Format(Resources.Messages.InvalidQueryValue, type, "type")); + } + url += "&type=" + type; } if (!string.IsNullOrEmpty(status)) { + if (validate && !ValidateBrowseParam(Resources.Constants.BrowseStatus, status)) + { + throw new ArgumentException(string.Format(Resources.Messages.InvalidQueryValue, status, "status")); + } + url += "&status=" + status; } return url; } - /// - /// Creates a web service browse template. - /// - private string CreateBrowseUrl(string entity, string relatedEntity, string mbid, int limit, int offset, string inc) - { - return string.Format(BrowseTemplate, entity, relatedEntity, mbid, limit, offset, inc); - } - /// /// Creates a web service search template. /// @@ -91,7 +87,31 @@ public string CreateSearchUrl(string entity, string query, int limit, int offset { query = WebUtility.UrlEncode(query); - return string.Format(SearchTemplate, entity, query, limit, offset); + var url = string.Format(SearchTemplate, entity, query); + + url = AddOptionalParams(url, limit, offset, null); + + return url; + } + + private string AddOptionalParams(string url, int limit, int offset, params string[] inc) + { + if (limit != DEFAULT_LIMIT) + { + url += "&limit=" + limit; + } + + if (offset != DEFAULT_OFFSET) + { + url += "&offset=" + offset; + } + + if (inc != null && inc.Length > 0) + { + url += "&inc=" + string.Join("+", inc); + } + + return url; } private bool ValidateBrowseParam(string availableParams, string value) @@ -103,10 +123,10 @@ private bool ValidateBrowseParam(string availableParams, string value) if (value.IndexOf('|') > 0) { - return value.Split('|').All(s => availableParams.IndexOf("+" + s + "+") >= 0); + return value.Split('|').All(s => availableParams.Contains("+" + s + "+")); } - return availableParams.IndexOf("+" + value + "+") >= 0; + return availableParams.Contains("+" + value + "+"); } } } diff --git a/src/Hqub.MusicBrainz/WebServiceException.cs b/src/Hqub.MusicBrainz/WebServiceException.cs index f0685f8..e72b0e7 100644 --- a/src/Hqub.MusicBrainz/WebServiceException.cs +++ b/src/Hqub.MusicBrainz/WebServiceException.cs @@ -12,13 +12,13 @@ public sealed class WebServiceException : Exception /// /// Gets the response HTTP status code. /// - public readonly HttpStatusCode StatusCode; + public HttpStatusCode StatusCode { get; } /// /// Gets the requested uri. /// - public readonly string Uri; - + public string Uri { get; } + /// /// Initializes a new instance of the class. ///