Ability to blacklist and rename indexer urls via services.

This commit is contained in:
Taloth Saldono 2017-05-06 14:02:59 +02:00
parent 766520b851
commit 7b4cb4145d
9 changed files with 421 additions and 5 deletions

View File

@ -366,6 +366,7 @@
<Compile Include="Qualities\QualityModelComparerFixture.cs" />
<Compile Include="RootFolderTests\RootFolderServiceFixture.cs" />
<Compile Include="SeriesStatsTests\SeriesStatisticsFixture.cs" />
<Compile Include="SkyhookNotifications\SkyhookNotificationServiceFixture.cs" />
<Compile Include="ThingiProvider\ProviderBaseFixture.cs" />
<Compile Include="ThingiProviderTests\NullConfigFixture.cs" />
<Compile Include="TvTests\EpisodeServiceTests\FindEpisodeByTitleFixture.cs" />

View File

@ -0,0 +1,172 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.SkyhookNotifications;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.SkyhookNotifications
{
public class SkyhookNotificationServiceFixture : CoreTest<SkyhookNotificationService>
{
private static readonly Version _previousVersion = new Version(BuildInfo.Version.Major, BuildInfo.Version.Minor, BuildInfo.Version.Build, BuildInfo.Version.Revision - 1);
private static readonly Version _currentVersion = BuildInfo.Version;
private static readonly Version _nextVersion = new Version(BuildInfo.Version.Major, BuildInfo.Version.Minor, BuildInfo.Version.Build, BuildInfo.Version.Revision + 1);
private List<SkyhookNotification> _expiredNotifications;
private List<SkyhookNotification> _currentNotifications;
private List<SkyhookNotification> _futureNotifications;
private List<SkyhookNotification> _urlNotifications;
[SetUp]
public void SetUp()
{
_expiredNotifications = new List<SkyhookNotification>
{
new SkyhookNotification
{
Id = 1,
Type = SkyhookNotificationType.Notification,
Title = "Expired Notification",
MaximumVersion = _previousVersion.ToString()
}
};
_currentNotifications = new List<SkyhookNotification>
{
new SkyhookNotification
{
Id = 2,
Type = SkyhookNotificationType.Notification,
Title = "Timeless current Notification"
},
new SkyhookNotification
{
Id = 2,
Type = SkyhookNotificationType.Notification,
Title = "Ending current Notification",
MaximumVersion = _currentVersion.ToString()
},
new SkyhookNotification
{
Id = 2,
Type = SkyhookNotificationType.Notification,
Title = "Ending future Notification",
MaximumVersion = _nextVersion.ToString()
},
new SkyhookNotification
{
Id = 2,
Type = SkyhookNotificationType.Notification,
Title = "Starting previous Notification",
MinimumVersion = _previousVersion.ToString()
},
new SkyhookNotification
{
Id = 2,
Type = SkyhookNotificationType.Notification,
Title = "Starting current Notification",
MinimumVersion = _currentVersion.ToString()
}
};
_futureNotifications = new List<SkyhookNotification>
{
new SkyhookNotification
{
Id = 3,
Type = SkyhookNotificationType.Notification,
Title = "Future Notification",
MinimumVersion = _nextVersion.ToString()
}
};
_urlNotifications = new List<SkyhookNotification>
{
new SkyhookNotification
{
Id = 3,
Type = SkyhookNotificationType.UrlBlacklist,
Title = "Future Notification"
},
new SkyhookNotification
{
Id = 3,
Type = SkyhookNotificationType.UrlReplace,
Title = "Future Notification"
}
};
}
private void GivenNotifications(List<SkyhookNotification> notifications)
{
Mocker.GetMock<ISkyhookNotificationProxy>()
.Setup(v => v.GetNotifications())
.Returns(notifications);
}
[Test]
public void should_not_return_expired_notifications()
{
GivenNotifications(_expiredNotifications);
Subject.GetUserNotifications().Should().BeEmpty();
}
[Test]
public void should_not_return_future_notifications()
{
GivenNotifications(_futureNotifications);
Subject.GetUserNotifications().Should().BeEmpty();
}
[Test]
public void should_return_current_notifications()
{
GivenNotifications(_currentNotifications);
Subject.GetUserNotifications().Should().HaveCount(_currentNotifications.Count);
}
[Test]
public void should_not_return_user_notifications()
{
GivenNotifications(_currentNotifications);
Subject.GetUrlNotifications().Should().BeEmpty();
}
[Test]
public void should_not_return_url_notifications()
{
GivenNotifications(_urlNotifications);
Subject.GetUserNotifications().Should().BeEmpty();
}
[Test]
public void should_return_url_notifications()
{
GivenNotifications(_urlNotifications);
Subject.GetUrlNotifications().Should().HaveCount(_urlNotifications.Count);
}
[Test]
public void should_cache_api_result()
{
GivenNotifications(_urlNotifications);
Subject.GetUrlNotifications();
Subject.GetUrlNotifications();
Mocker.GetMock<ISkyhookNotificationProxy>()
.Verify(v => v.GetNotifications(), Times.Once());
}
}
}

View File

@ -2,7 +2,9 @@
using System.Linq;
using NLog;
using NzbDrone.Common.Composition;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.SkyhookNotifications;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers
@ -15,10 +17,12 @@ namespace NzbDrone.Core.Indexers
public class IndexerFactory : ProviderFactory<IIndexer, IndexerDefinition>, IIndexerFactory
{
private readonly ISubstituteIndexerUrl _substituteIndexerUrl;
private readonly IIndexerStatusService _indexerStatusService;
private readonly Logger _logger;
public IndexerFactory(IIndexerStatusService indexerStatusService,
public IndexerFactory(ISubstituteIndexerUrl replaceIndexerUrl,
IIndexerStatusService indexerStatusService,
IIndexerRepository providerRepository,
IEnumerable<IIndexer> providers,
IContainer container,
@ -26,6 +30,7 @@ namespace NzbDrone.Core.Indexers
Logger logger)
: base(providerRepository, providers, container, eventAggregator, logger)
{
_substituteIndexerUrl = replaceIndexerUrl;
_indexerStatusService = indexerStatusService;
_logger = logger;
}
@ -50,7 +55,7 @@ namespace NzbDrone.Core.Indexers
if (filterBlockedIndexers)
{
return FilterBlockedIndexers(enabledIndexers).ToList();
return FilterBlockedIndexers(SubstituteIndexerUrls(enabledIndexers)).ToList();
}
return enabledIndexers.ToList();
@ -62,12 +67,31 @@ namespace NzbDrone.Core.Indexers
if (filterBlockedIndexers)
{
return FilterBlockedIndexers(enabledIndexers).ToList();
return FilterBlockedIndexers(SubstituteIndexerUrls(enabledIndexers)).ToList();
}
return enabledIndexers.ToList();
}
private IEnumerable<IIndexer> SubstituteIndexerUrls(IEnumerable<IIndexer> indexers)
{
foreach (var indexer in indexers)
{
var settings = (IIndexerSettings)indexer.Definition.Settings;
if (settings.BaseUrl.IsNotNullOrWhiteSpace())
{
var newBaseUrl = _substituteIndexerUrl.SubstituteUrl(settings.BaseUrl);
if (newBaseUrl != settings.BaseUrl)
{
_logger.Debug("Substituted indexer {0} url {1} with {2} since services blacklisted it.", indexer.Definition.Name, settings.BaseUrl, newBaseUrl);
settings.BaseUrl = newBaseUrl;
}
}
yield return indexer;
}
}
private IEnumerable<IIndexer> FilterBlockedIndexers(IEnumerable<IIndexer> indexers)
{
var blockedIndexers = _indexerStatusService.GetBlockedIndexers().ToDictionary(v => v.ProviderId, v => v);

View File

@ -1063,6 +1063,11 @@
<Compile Include="SeriesStats\SeriesStatistics.cs" />
<Compile Include="SeriesStats\SeriesStatisticsRepository.cs" />
<Compile Include="SeriesStats\SeriesStatisticsService.cs" />
<Compile Include="SkyhookNotifications\SkyhookNotification.cs" />
<Compile Include="SkyhookNotifications\SkyhookNotificationService.cs" />
<Compile Include="SkyhookNotifications\SkyhookNotificationType.cs" />
<Compile Include="SkyhookNotifications\SkyhookNotificationProxy.cs" />
<Compile Include="SkyhookNotifications\SubstituteUrlService.cs" />
<Compile Include="Tags\Tag.cs" />
<Compile Include="Tags\TagRepository.cs" />
<Compile Include="Tags\TagService.cs" />

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.SkyhookNotifications
{
public class SkyhookNotification
{
public int Id { get; set; }
public string MinimumVersion { get; set; }
public string MaximumVersion { get; set; }
public SkyhookNotificationType Type { get; set; }
public string Title { get; set; }
public string Message { get; set; }
public string RegexMatch { get; set; }
public string RegexReplace { get; set; }
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NLog;
using NzbDrone.Common.Cloud;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Http;
namespace NzbDrone.Core.SkyhookNotifications
{
public interface ISkyhookNotificationProxy
{
List<SkyhookNotification> GetNotifications();
}
public class SkyhookNotificationProxy : ISkyhookNotificationProxy
{
private readonly IHttpClient _httpClient;
private readonly IHttpRequestBuilderFactory _requestBuilder;
private readonly Logger _logger;
public SkyhookNotificationProxy(IHttpClient httpClient, ISonarrCloudRequestBuilder requestBuilder, Logger logger)
{
_httpClient = httpClient;
_requestBuilder = requestBuilder.Services;
_logger = logger;
}
public List<SkyhookNotification> GetNotifications()
{
var notificationsRequest = _requestBuilder.Create()
.Resource("/notifications")
.AddQueryParam("version", BuildInfo.Version)
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
.Build();
try
{
var response = _httpClient.Get<List<SkyhookNotification>>(notificationsRequest);
return response.Resource;
}
catch (Exception ex)
{
_logger.Warn(ex, "Failed to get information update from {0}", notificationsRequest.Url.Host);
return new List<SkyhookNotification>();
}
}
}
}

View File

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Core.SkyhookNotifications
{
public interface ISkyhookNotificationService
{
List<SkyhookNotification> GetUserNotifications();
List<SkyhookNotification> GetUrlNotifications();
}
public class SkyhookNotificationService : ISkyhookNotificationService
{
private readonly ISkyhookNotificationProxy _proxy;
private readonly Logger _logger;
private readonly ICached<List<SkyhookNotification>> _cache;
private readonly TimeSpan CacheExpiry = TimeSpan.FromHours(12);
public SkyhookNotificationService(ISkyhookNotificationProxy proxy, ICacheManager cacheManager, Logger logger)
{
_proxy = proxy;
_logger = logger;
_cache = cacheManager.GetCache<List<SkyhookNotification>>(GetType());
}
private List<SkyhookNotification> GetNotifications(string key)
{
var result = _cache.Find(key);
if (result == null)
{
var all = _proxy.GetNotifications().Where(FilterVersion).ToList();
var user = all.Where(v => v.Type == SkyhookNotificationType.Notification).ToList();
var url = all.Where(v => v.Type == SkyhookNotificationType.UrlBlacklist || v.Type == SkyhookNotificationType.UrlReplace).ToList();
_cache.Set("all", all, CacheExpiry);
_cache.Set("user", user, CacheExpiry);
_cache.Set("url", url, CacheExpiry);
}
return _cache.Find(key);
}
public List<SkyhookNotification> GetUserNotifications()
{
return GetNotifications("user");
}
public List<SkyhookNotification> GetUrlNotifications()
{
return GetNotifications("url");
}
private bool FilterVersion(SkyhookNotification notification)
{
if (notification.MinimumVersion != null && BuildInfo.Version < Version.Parse(notification.MinimumVersion))
{
return false;
}
if (notification.MaximumVersion != null && BuildInfo.Version > Version.Parse(notification.MaximumVersion))
{
return false;
}
return true;
}
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Linq;
namespace NzbDrone.Core.SkyhookNotifications
{
public enum SkyhookNotificationType
{
// Notification for the user alone.
Notification = 1,
// Indexer urls matching the RegexMatch are automatically set to temporarily disabled and never contacted.
UrlBlacklist = 2,
// Indexer urls matching the RegexMatch are replaced with RegexReplace.
UrlReplace = 3
}
}

View File

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace NzbDrone.Core.SkyhookNotifications
{
public interface ISubstituteIndexerUrl
{
string SubstituteUrl(string baseUrl);
}
public class SubstituteUrlService : ISubstituteIndexerUrl
{
private readonly ISkyhookNotificationService _notificationService;
public SubstituteUrlService(ISkyhookNotificationService notificationService)
{
_notificationService = notificationService;
}
public string SubstituteUrl(string baseUrl)
{
foreach (var action in _notificationService.GetUrlNotifications())
{
if (action.Type == SkyhookNotificationType.UrlBlacklist)
{
if (action.RegexMatch == null) continue;
if (Regex.IsMatch(baseUrl, action.RegexMatch))
{
return null;
}
}
else if (action.Type == SkyhookNotificationType.UrlReplace)
{
if (action.RegexMatch == null) continue;
if (action.RegexReplace == null) continue;
if (Regex.IsMatch(baseUrl, action.RegexMatch))
{
return Regex.Replace(baseUrl, action.RegexMatch, action.RegexReplace);
}
}
}
return baseUrl;
}
}
}