mirror of
https://github.com/lidarr/Lidarr
synced 2025-02-26 07:42:49 +00:00
Fixed: Handle ratelimit response from acoustid
This commit is contained in:
parent
2f96c962a5
commit
906fdb8365
2 changed files with 113 additions and 23 deletions
|
@ -216,7 +216,7 @@ public void should_not_throw_if_api_returns_html()
|
||||||
Subject.Lookup(new List<LocalTrack> { localTrack }, 0.5);
|
Subject.Lookup(new List<LocalTrack> { localTrack }, 0.5);
|
||||||
localTrack.AcoustIdResults.Should().BeNull();
|
localTrack.AcoustIdResults.Should().BeNull();
|
||||||
|
|
||||||
ExceptionVerification.ExpectedWarns(1);
|
ExceptionVerification.ExpectedWarns(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -232,5 +232,43 @@ public void should_not_throw_if_api_times_out()
|
||||||
|
|
||||||
ExceptionVerification.ExpectedWarns(1);
|
ExceptionVerification.ExpectedWarns(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_retry_if_too_many_requests()
|
||||||
|
{
|
||||||
|
var error = "{\"error\": {\"code\": 14, \"message\": \"rate limit (3 requests per second) exceeded, try again later\"}, \"status\": \"error\"}";
|
||||||
|
var response = "{\"fingerprints\": [{\"index\": \"0\", \"results\": [{\"id\": \"a9b004fe-e161-417c-9f9e-443e4525334d\", \"recordings\": [{\"id\": \"209a4536-97ac-4e8a-aff1-1d39d029044b\"}, {\"id\": \"30f3f33e-8d0c-4e69-8539-cbd701d18f28\"}], \"score\": 0.940997}, {\"id\": \"fe0a9bec-2633-4c37-89be-b5d295b68a00\", \"score\": 0.763876}, {\"id\": \"18eab869-51dc-4948-83f7-4d8d441d5a1b\", \"score\": 0.490447}]}], \"status\": \"ok\"}";
|
||||||
|
Mocker.GetMock<IHttpClient>()
|
||||||
|
.SetupSequence(o => o.Post<LookupResponse>(It.IsAny<HttpRequest>()))
|
||||||
|
.Returns(new HttpResponse<LookupResponse>(new HttpResponse(new HttpRequest("dummy"), new HttpHeader(), error, HttpStatusCode.ServiceUnavailable)))
|
||||||
|
.Returns(new HttpResponse<LookupResponse>(new HttpResponse(new HttpRequest("dummy"), new HttpHeader(), response, HttpStatusCode.OK)));
|
||||||
|
|
||||||
|
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Media", "nin.mp3");
|
||||||
|
var localTrack = new LocalTrack { Path = path };
|
||||||
|
Subject.Lookup(new List<LocalTrack> { localTrack }, 0.5);
|
||||||
|
localTrack.AcoustIdResults.Should().NotBeNull();
|
||||||
|
localTrack.AcoustIdResults.Should().Contain("30f3f33e-8d0c-4e69-8539-cbd701d18f28");
|
||||||
|
|
||||||
|
Mocker.GetMock<IHttpClient>()
|
||||||
|
.Verify(x => x.Post<LookupResponse>(It.IsAny<HttpRequest>()), Times.Exactly(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_retry_indefinitely_if_too_many_requests()
|
||||||
|
{
|
||||||
|
var error = "{\"error\": {\"code\": 14, \"message\": \"rate limit (3 requests per second) exceeded, try again later\"}, \"status\": \"error\"}";
|
||||||
|
Mocker.GetMock<IHttpClient>()
|
||||||
|
.Setup(o => o.Post<LookupResponse>(It.IsAny<HttpRequest>()))
|
||||||
|
.Returns(new HttpResponse<LookupResponse>(new HttpResponse(new HttpRequest("dummy"), new HttpHeader(), error, HttpStatusCode.ServiceUnavailable)));
|
||||||
|
|
||||||
|
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Media", "nin.mp3");
|
||||||
|
var localTrack = new LocalTrack { Path = path };
|
||||||
|
|
||||||
|
Subject.Lookup(new List<LocalTrack> { localTrack }, 0.5);
|
||||||
|
localTrack.AcoustIdResults.Should().BeNull();
|
||||||
|
|
||||||
|
Mocker.GetMock<IHttpClient>()
|
||||||
|
.Verify(x => x.Post<LookupResponse>(It.IsAny<HttpRequest>()), Times.Exactly(4));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
using System.IO;
|
|
||||||
using NLog;
|
|
||||||
using NzbDrone.Core.Parser.Model;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using NzbDrone.Common.Http;
|
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Text;
|
|
||||||
using NzbDrone.Common.Serializer;
|
|
||||||
using System;
|
using System;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using NLog;
|
||||||
using NzbDrone.Common.Cache;
|
using NzbDrone.Common.Cache;
|
||||||
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Common.Serializer;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Parser
|
namespace NzbDrone.Core.Parser
|
||||||
{
|
{
|
||||||
|
@ -347,6 +347,13 @@ public void Lookup(List<Tuple<LocalTrack, AcoustId>> files, double threshold)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var request = GenerateRequest(toLookup);
|
||||||
|
var response = GetResponse(request);
|
||||||
|
ParseResponse(response, toLookup, threshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpRequest GenerateRequest(List<Tuple<LocalTrack, AcoustId>> toLookup)
|
||||||
|
{
|
||||||
var httpRequest = _customerRequestBuilder.Create()
|
var httpRequest = _customerRequestBuilder.Create()
|
||||||
.WithRateLimit(0.334)
|
.WithRateLimit(0.334)
|
||||||
.Build();
|
.Build();
|
||||||
|
@ -364,32 +371,41 @@ public void Lookup(List<Tuple<LocalTrack, AcoustId>> files, double threshold)
|
||||||
httpRequest.SuppressHttpError = true;
|
httpRequest.SuppressHttpError = true;
|
||||||
httpRequest.RequestTimeout = TimeSpan.FromSeconds(5);
|
httpRequest.RequestTimeout = TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
|
return httpRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LookupResponse GetResponse(HttpRequest request, int retry = 3)
|
||||||
|
{
|
||||||
HttpResponse<LookupResponse> httpResponse;
|
HttpResponse<LookupResponse> httpResponse;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
httpResponse = _httpClient.Post<LookupResponse>(httpRequest);
|
httpResponse = _httpClient.Post<LookupResponse>(request);
|
||||||
}
|
}
|
||||||
catch (UnexpectedHtmlContentException e)
|
catch (UnexpectedHtmlContentException e)
|
||||||
{
|
{
|
||||||
_logger.Warn(e, "AcoustId API gave invalid response");
|
_logger.Warn(e, "AcoustId API gave invalid response");
|
||||||
return;
|
return retry > 0 ? GetResponse(request, retry - 1) : null;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.Warn(e, "AcoustId API lookup failed");
|
_logger.Warn(e, "AcoustId API lookup failed");
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var response = httpResponse.Resource;
|
var response = httpResponse.Resource;
|
||||||
|
|
||||||
// The API will give errors if fingerprint isn't found or is invalid.
|
|
||||||
// We don't want to stop the entire import because the fingerprinting failed
|
|
||||||
// so just log and return.
|
|
||||||
if (httpResponse.HasHttpError || (response != null && response.Status != "ok"))
|
if (httpResponse.HasHttpError || (response != null && response.Status != "ok"))
|
||||||
{
|
{
|
||||||
if (response != null && response.Error != null)
|
if (response?.Error != null)
|
||||||
{
|
{
|
||||||
|
if (response.Error.Code == AcoustIdErrorCode.TooManyRequests && retry > 0)
|
||||||
|
{
|
||||||
|
_logger.Trace($"Too many requests, retrying in 1s");
|
||||||
|
Thread.Sleep(TimeSpan.FromSeconds(1));
|
||||||
|
return GetResponse(request, retry - 1);
|
||||||
|
}
|
||||||
|
|
||||||
_logger.Debug($"Webservice error {response.Error.Code}: {response.Error.Message}");
|
_logger.Debug($"Webservice error {response.Error.Code}: {response.Error.Message}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -397,9 +413,22 @@ public void Lookup(List<Tuple<LocalTrack, AcoustId>> files, double threshold)
|
||||||
_logger.Warn("HTTP Error - {0}", httpResponse);
|
_logger.Warn("HTTP Error - {0}", httpResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseResponse(LookupResponse response, List<Tuple<LocalTrack, AcoustId>> toLookup, double threshold)
|
||||||
|
{
|
||||||
|
if (response == null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The API will give errors if fingerprint isn't found or is invalid.
|
||||||
|
// We don't want to stop the entire import because the fingerprinting failed
|
||||||
|
// so just log and return.
|
||||||
foreach (var fileResponse in response.Fingerprints)
|
foreach (var fileResponse in response.Fingerprints)
|
||||||
{
|
{
|
||||||
if (fileResponse.Results.Count == 0)
|
if (fileResponse.Results.Count == 0)
|
||||||
|
@ -434,10 +463,33 @@ public class LookupResponse
|
||||||
public List<LookupResultListItem> Fingerprints { get; set; }
|
public List<LookupResultListItem> Fingerprints { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum AcoustIdErrorCode
|
||||||
|
{
|
||||||
|
// https://github.com/acoustid/acoustid-server/blob/f671339ad9ab049c4d4361d3eadb6660a8fe4dda/acoustid/api/errors.py#L10
|
||||||
|
UnknownFormat = 1,
|
||||||
|
MissingParameter = 2,
|
||||||
|
InvalidFingerprint = 3,
|
||||||
|
InvalidApikey = 4,
|
||||||
|
Internal = 5,
|
||||||
|
InvalidUserApikey = 6,
|
||||||
|
InvalidUuid = 7,
|
||||||
|
InvalidDuration = 8,
|
||||||
|
InvalidBitrate = 9,
|
||||||
|
InvalidForeignid = 10,
|
||||||
|
InvalidMaxDurationDiff = 11,
|
||||||
|
NotAllowed = 12,
|
||||||
|
ServiceUnavailable = 13,
|
||||||
|
TooManyRequests = 14,
|
||||||
|
InvalidMusicbrainzAccessToken = 15,
|
||||||
|
InsecureRequest = 16,
|
||||||
|
UnknownApplication = 17,
|
||||||
|
FingerprintNotFound = 18
|
||||||
|
}
|
||||||
|
|
||||||
public class LookupError
|
public class LookupError
|
||||||
{
|
{
|
||||||
public string Message { get; set; }
|
public string Message { get; set; }
|
||||||
public int Code { get; set; }
|
public AcoustIdErrorCode Code { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LookupResultListItem
|
public class LookupResultListItem
|
||||||
|
|
Loading…
Reference in a new issue