Skip to content

Commit

Permalink
Merge pull request #16 from skuill/feature/bundle_14_15
Browse files Browse the repository at this point in the history
Bundle #14 #15 Ability to search for a specific provider. Added status codes and response message in a search result.
  • Loading branch information
skuill authored Dec 11, 2023
2 parents 9dbf3b7 + e5d417f commit 0c71753
Show file tree
Hide file tree
Showing 20 changed files with 544 additions and 37 deletions.
22 changes: 22 additions & 0 deletions LyricsScraperNET/Common/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace LyricsScraperNET.Common
{
internal static class Constants
{
internal static class ResponseMessages
{
internal static readonly string ExternalProvidersListIsEmpty = "Empty external providers list! Please set any external provider first";

internal static readonly string ExternalProvidersAreDisabled = "All external providers are disabled. Searching lyrics is disabled";

internal static readonly string NotFoundLyric = "Can't find lyric for the search request";

internal static readonly string ExternalProviderForRequestNotSpecified = "The provider specified in the request is disabled or has not been configured for the client";

internal static readonly string SearchRequestIsEmpty = "Search request is empty";

internal static readonly string ArtistAndSongSearchRequestFieldsAreEmpty = "The ArtistAndSongSearchRequest not valid and contains one or more empty fields";

internal static readonly string UriSearchRequestFieldsAreEmpty = "The UriSearchRequest not valid and contains one or more empty fields";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace LyricsScraperNET.Extensions
{
public static class ExternalProviderOptionsExtensions
internal static class ExternalProviderOptionsExtensions
{
public static bool TryGetApiKeyFromOptions(this IExternalProviderOptions options, out string apiKey)
{
Expand Down
10 changes: 10 additions & 0 deletions LyricsScraperNET/Extensions/ExternalProviderTypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using LyricsScraperNET.Providers.Models;

namespace LyricsScraperNET.Extensions
{
internal static class ExternalProviderTypeExtensions
{
public static bool IsNoneProviderType(this ExternalProviderType providerType)
=> providerType == ExternalProviderType.None;
}
}
21 changes: 21 additions & 0 deletions LyricsScraperNET/Extensions/SearchRequestExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using LyricsScraperNET.Models.Requests;
using LyricsScraperNET.Providers.Models;

namespace LyricsScraperNET.Extensions
{
internal static class SearchRequestExtensions
{
public static ExternalProviderType GetProviderTypeFromRequest(this SearchRequest searchRequest)
{
switch (searchRequest)
{
case ArtistAndSongSearchRequest artistAndSongSearchRequest:
return artistAndSongSearchRequest.Provider;
case UriSearchRequest uriSearchRequest:
return uriSearchRequest.Provider;
default:
return ExternalProviderType.None;
}
}
}
}
33 changes: 33 additions & 0 deletions LyricsScraperNET/Extensions/SearchResultExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using LyricsScraperNET.Models.Responses;

namespace LyricsScraperNET.Extensions
{
internal static class SearchResultExtensions
{
internal static SearchResult AddErrorMessage(this SearchResult searchResult, string responseMessage)
{
searchResult.ResponseStatusCode = ResponseStatusCode.Error;
return searchResult.AppendResponseMessage(responseMessage);
}

internal static SearchResult AddNoDataFoundMessage(this SearchResult searchResult, string responseMessage)
{
searchResult.ResponseStatusCode = ResponseStatusCode.NoDataFound;
return searchResult.AppendResponseMessage(responseMessage);
}

internal static SearchResult AddBadRequestMessage(this SearchResult searchResult, string responseMessage)
{
searchResult.ResponseStatusCode = ResponseStatusCode.BadRequest;
return searchResult.AppendResponseMessage(responseMessage);
}

internal static SearchResult AppendResponseMessage(this SearchResult searchResult, string responseMessage)
{
searchResult.ResponseMessage = !string.IsNullOrWhiteSpace(searchResult.ResponseMessage)
? string.Join("; ", new[] { searchResult.ResponseMessage, responseMessage })
: responseMessage;
return searchResult;
}
}
}
158 changes: 128 additions & 30 deletions LyricsScraperNET/LyricsScraperClient.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using LyricsScraperNET.Configuration;
using LyricsScraperNET.Common;
using LyricsScraperNET.Configuration;
using LyricsScraperNET.Extensions;
using LyricsScraperNET.Helpers;
using LyricsScraperNET.Models.Requests;
using LyricsScraperNET.Models.Responses;
using LyricsScraperNET.Providers.Abstract;
using LyricsScraperNET.Providers.Models;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
Expand All @@ -23,8 +26,8 @@ public sealed class LyricsScraperClient : ILyricsScraperClient

public IExternalProvider this[ExternalProviderType providerType]
{
get => !IsEmptyProviders()
? _externalProviders.FirstOrDefault(p => p.Options.ExternalProviderType == providerType)
get => IsProviderAvailable(providerType)

Check warning on line 29 in LyricsScraperNET/LyricsScraperClient.cs

View workflow job for this annotation

GitHub Actions / lyrics_scraper_net-cicd

Possible null reference return.

Check warning on line 29 in LyricsScraperNET/LyricsScraperClient.cs

View workflow job for this annotation

GitHub Actions / lyrics_scraper_net-cicd

Possible null reference return.
? _externalProviders.First(p => p.Options.ExternalProviderType == providerType)
: null;
}

Expand All @@ -50,86 +53,169 @@ public LyricsScraperClient(ILogger<LyricsScraperClient> logger,

public SearchResult SearchLyric(SearchRequest searchRequest)
{
if (!ValidateRequest())
return new SearchResult();
var searchResult = new SearchResult();

foreach (var externalProvider in _externalProviders.OrderByDescending(x => x.SearchPriority))
if (!ValidSearchRequest(searchRequest, out var badRequestErrorMessage))
{
var searchResult = externalProvider.SearchLyric(searchRequest);
if (!searchResult.IsEmpty())
searchResult.AddBadRequestMessage(badRequestErrorMessage);
return searchResult;
}

if (!ValidClientConfiguration(out var errorMessage))
{
searchResult.AddNoDataFoundMessage(errorMessage);
return searchResult;
}

foreach (var externalProvider in GetAvailableProvidersForSearchRequest(searchRequest))
{
var providerSearchResult = externalProvider.SearchLyric(searchRequest);
if (!providerSearchResult.IsEmpty())
{
return searchResult;
return providerSearchResult;
}
_logger?.LogWarning($"Can't find lyric by provider: {externalProvider}.");
}

searchResult.AddNoDataFoundMessage(Constants.ResponseMessages.NotFoundLyric);
_logger?.LogError($"Can't find lyrics for searchRequest: {searchRequest}.");
return new SearchResult();

return searchResult;
}

public async Task<SearchResult> SearchLyricAsync(SearchRequest searchRequest)
{
if (!ValidateRequest())
return new SearchResult();
var searchResult = new SearchResult();

if (!ValidSearchRequest(searchRequest, out var badRequestErrorMessage))
{
searchResult.AddBadRequestMessage(badRequestErrorMessage);
return searchResult;
}

if (!ValidClientConfiguration(out var errorMessage))
{
searchResult.AddNoDataFoundMessage(errorMessage);
return searchResult;
}

foreach (var externalProvider in _externalProviders.OrderByDescending(x => x.SearchPriority))
foreach (var externalProvider in GetAvailableProvidersForSearchRequest(searchRequest))
{
var searchResult = await externalProvider.SearchLyricAsync(searchRequest);
if (!searchResult.IsEmpty())
var providerSearchResult = await externalProvider.SearchLyricAsync(searchRequest);
if (!providerSearchResult.IsEmpty())
{
return searchResult;
return providerSearchResult;
}
_logger?.LogWarning($"Can't find lyric by provider: {externalProvider}.");
}

searchResult.AddNoDataFoundMessage(Constants.ResponseMessages.NotFoundLyric);
_logger?.LogError($"Can't find lyrics for searchRequest: {searchRequest}.");
return new SearchResult();

return searchResult;
}

private IEnumerable<IExternalProvider> GetAvailableProvidersForSearchRequest(SearchRequest searchRequest)
{
var searchRequestExternalProvider = searchRequest.GetProviderTypeFromRequest();

if (searchRequestExternalProvider.IsNoneProviderType())
return _externalProviders.Where(p => p.IsEnabled).OrderByDescending(p => p.SearchPriority);

var availableProviders = _externalProviders.Where(p => p.IsEnabled && p.Options.ExternalProviderType == searchRequestExternalProvider);

if (availableProviders.Any())
return availableProviders.OrderByDescending(p => p.SearchPriority);

return Array.Empty<IExternalProvider>();
}

private bool ValidateRequest()
private bool ValidClientConfiguration(out string errorMessage)
{
string error = string.Empty;
errorMessage = string.Empty;
LogLevel logLevel = LogLevel.Error;

if (IsEmptyProviders())
if (IsEmptyProvidersList())
{
error = "Empty providers list! Please set any external provider first.";
errorMessage = Constants.ResponseMessages.ExternalProvidersListIsEmpty;
}
else if (!IsEnabled)
{
error = "All external providers is disabled. Searching lyrics is disabled.";
errorMessage = Constants.ResponseMessages.ExternalProvidersAreDisabled;
logLevel = LogLevel.Debug;
}

if (!string.IsNullOrWhiteSpace(error))
if (!string.IsNullOrWhiteSpace(errorMessage))
{
_logger?.Log(logLevel, errorMessage);
return false;
}
return true;
}

private bool ValidSearchRequest(SearchRequest searchRequest, out string errorMessage)
{
errorMessage = string.Empty;
LogLevel logLevel = LogLevel.Error;

if (searchRequest == null)
{
errorMessage = Constants.ResponseMessages.SearchRequestIsEmpty;
_logger?.Log(logLevel, errorMessage);
return false;
}

switch (searchRequest)
{
_logger?.Log(logLevel, error);
case ArtistAndSongSearchRequest artistAndSongSearchRequest:
errorMessage = string.IsNullOrEmpty(artistAndSongSearchRequest.Artist) || string.IsNullOrEmpty(artistAndSongSearchRequest.Song)
? Constants.ResponseMessages.ArtistAndSongSearchRequestFieldsAreEmpty
: string.Empty;
break;
case UriSearchRequest uriSearchRequest:
errorMessage = uriSearchRequest.Uri == null
? Constants.ResponseMessages.UriSearchRequestFieldsAreEmpty
: string.Empty;
break;
}

var searchRequestExternalProvider = searchRequest.GetProviderTypeFromRequest();

if (!searchRequestExternalProvider.IsNoneProviderType() && !IsProviderEnabled(searchRequestExternalProvider))
{
errorMessage = Constants.ResponseMessages.ExternalProviderForRequestNotSpecified;
}

if (!string.IsNullOrWhiteSpace(errorMessage))
{
_logger?.Log(logLevel, errorMessage);
return false;
}

return true;
}

public void AddProvider(IExternalProvider provider)
{
if (IsEmptyProviders())
if (IsEmptyProvidersList())
_externalProviders = new List<IExternalProvider>();
if (!_externalProviders.Contains(provider))
_externalProviders.Add(provider);
else
_logger?.LogWarning($"External provider {provider} already added");
}

private bool IsEmptyProviders() => _externalProviders == null || !_externalProviders.Any();

public void RemoveProvider(ExternalProviderType providerType)
{
if (IsEmptyProviders())
if (providerType.IsNoneProviderType() || IsEmptyProvidersList())
return;

_externalProviders.RemoveAll(x => x.Options.ExternalProviderType == providerType);
}

public void Enable()
{
if (IsEmptyProviders())
if (IsEmptyProvidersList())
return;

foreach (var provider in _externalProviders)
Expand All @@ -140,13 +226,25 @@ public void Enable()

public void Disable()
{
if (IsEmptyProviders())
if (IsEmptyProvidersList())
return;

foreach (var provider in _externalProviders)
{
provider.Disable();
}
}

private bool IsEmptyProvidersList() => _externalProviders == null || !_externalProviders.Any();

private bool IsProviderAvailable(ExternalProviderType providerType)
=> !providerType.IsNoneProviderType()
&& !IsEmptyProvidersList()
&& _externalProviders.Any(p => p.Options.ExternalProviderType == providerType);

private bool IsProviderEnabled(ExternalProviderType providerType)
=> !providerType.IsNoneProviderType()
&& IsProviderAvailable(providerType)
&& this[providerType].IsEnabled;
}
}
25 changes: 24 additions & 1 deletion LyricsScraperNET/Models/Requests/ArtistAndSongSearchRequest.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,38 @@
namespace LyricsScraperNET.Models.Requests
using LyricsScraperNET.Providers.Models;

namespace LyricsScraperNET.Models.Requests
{
/// <summary>
/// Query model for searching lyrics by artist/band name and song/track title.
/// </summary>
public sealed class ArtistAndSongSearchRequest : SearchRequest
{
/// <summary>
/// Artist or band name.
/// </summary>
public string Artist { get; }

/// <summary>
/// Song or track title.
/// </summary>
public string Song { get; }

/// <summary>
/// The type of external provider for which lyrics will be searched.
/// By default, it is set to <see cref="ExternalProviderType.None"/> - the search will be performed across all available client providers.
/// </summary>
public ExternalProviderType Provider { get; } = ExternalProviderType.None;

public ArtistAndSongSearchRequest(string artist, string song)
{
Artist = artist;
Song = song;
}

public ArtistAndSongSearchRequest(string artist, string song, ExternalProviderType provider)
: this(artist, song)
{
Provider = provider;
}
}
}
Loading

0 comments on commit 0c71753

Please sign in to comment.