using System; using System.Net; using System.Threading; using System.Threading.Tasks; using MetaBrainz.Common; using MetaBrainz.MusicBrainz; using MetaBrainz.MusicBrainz.Interfaces.Entities; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Plugins.MusicBrainz; /// /// Helpers for talking to MusicBrainz with identifiers that are not guaranteed to be valid. /// internal static class MusicBrainzQueryExtensions { /// /// Parses a MusicBrainz identifier, which may come from user-supplied tags or NFO files and is therefore not /// guaranteed to be a valid GUID. /// /// The identifier to parse. /// The type of entity the identifier refers to, used for logging. /// The logger. /// The parsed identifier, or if it is missing or malformed. public static Guid? ParseMusicBrainzId(string? id, string entityType, ILogger logger) { if (string.IsNullOrWhiteSpace(id)) { return null; } if (!Guid.TryParse(id, out var parsedId)) { logger.LogDebug("Ignoring malformed MusicBrainz {EntityType} id {Id}", entityType, id); return null; } return parsedId; } /// /// Looks up a release, treating an unknown identifier as missing data rather than an error. /// /// The MusicBrainz query client. /// The release identifier. /// The additional data to include in the lookup. /// The logger. /// The cancellation token. /// The release, or if MusicBrainz does not have it. public static Task LookupReleaseOrNullAsync(this Query query, Guid releaseId, Include include, ILogger logger, CancellationToken cancellationToken) => NotFoundAsNullAsync( () => query.LookupReleaseAsync(releaseId, include, cancellationToken), "release", releaseId, logger); /// /// Looks up a release group, treating an unknown identifier as missing data rather than an error. /// /// The MusicBrainz query client. /// The release group identifier. /// The additional data to include in the lookup. /// The logger. /// The cancellation token. /// The release group, or if MusicBrainz does not have it. public static Task LookupReleaseGroupOrNullAsync(this Query query, Guid releaseGroupId, Include include, ILogger logger, CancellationToken cancellationToken) => NotFoundAsNullAsync( () => query.LookupReleaseGroupAsync(releaseGroupId, include, null, cancellationToken), "release group", releaseGroupId, logger); /// /// Looks up an artist, treating an unknown identifier as missing data rather than an error. /// /// The MusicBrainz query client. /// The artist identifier. /// The additional data to include in the lookup. /// The logger. /// The cancellation token. /// The artist, or if MusicBrainz does not have it. public static Task LookupArtistOrNullAsync(this Query query, Guid artistId, Include include, ILogger logger, CancellationToken cancellationToken) => NotFoundAsNullAsync( () => query.LookupArtistAsync(artistId, include, null, null, cancellationToken), "artist", artistId, logger); /// /// Runs a lookup, mapping a "not found" response to . Identifiers stored on a library item /// can refer to entities that no longer exist in MusicBrainz, which is not an error worth failing a refresh over. /// /// The type of entity being looked up. /// The lookup to run. /// The type of entity being looked up, used for logging. /// The identifier being looked up, used for logging. /// The logger. /// The entity, or if MusicBrainz does not have it. private static async Task NotFoundAsNullAsync(Func> lookup, string entityType, Guid id, ILogger logger) where T : class { try { return await lookup().ConfigureAwait(false); } catch (HttpError ex) when (ex.Status == HttpStatusCode.NotFound) { logger.LogDebug("MusicBrainz has no {EntityType} with id {Id}", entityType, id); return null; } } }