diff --git a/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContent.js b/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContent.js index 112c06948..3ddd0223c 100644 --- a/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContent.js +++ b/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContent.js @@ -43,6 +43,7 @@ function EditIndexerModalContent(props) { enableInteractiveSearch, supportsRss, supportsSearch, + tags, fields, priority, protocol, @@ -168,18 +169,30 @@ function EditIndexerModalContent(props) { advancedSettings={advancedSettings} isAdvanced={true} > - DownloadClient + {translate('DownloadClient')} + + + {translate('Tags')} + + + } diff --git a/frontend/src/Settings/Indexers/Indexers/Indexer.js b/frontend/src/Settings/Indexers/Indexers/Indexer.js index 93e8d2d57..05198dcf8 100644 --- a/frontend/src/Settings/Indexers/Indexers/Indexer.js +++ b/frontend/src/Settings/Indexers/Indexers/Indexer.js @@ -4,6 +4,7 @@ import Card from 'Components/Card'; import Label from 'Components/Label'; import IconButton from 'Components/Link/IconButton'; import ConfirmModal from 'Components/Modal/ConfirmModal'; +import TagList from 'Components/TagList'; import { icons, kinds } from 'Helpers/Props'; import translate from 'Utilities/String/translate'; import EditIndexerModalConnector from './EditIndexerModalConnector'; @@ -68,6 +69,8 @@ class Indexer extends Component { enableRss, enableAutomaticSearch, enableInteractiveSearch, + tags, + tagList, supportsRss, supportsSearch, priority, @@ -133,6 +136,11 @@ class Indexer extends Component { } + + indexers + createTagsSelector(), + (indexers, tagList) => { + return { + ...indexers, + tagList + }; + } ); } diff --git a/frontend/src/Settings/Tags/Details/TagDetailsModalContent.js b/frontend/src/Settings/Tags/Details/TagDetailsModalContent.js index a8a08d719..0168d9ae3 100644 --- a/frontend/src/Settings/Tags/Details/TagDetailsModalContent.js +++ b/frontend/src/Settings/Tags/Details/TagDetailsModalContent.js @@ -22,6 +22,7 @@ function TagDetailsModalContent(props) { importLists, notifications, releaseProfiles, + indexers, onModalClose, onDeleteTagPress } = props; @@ -41,7 +42,7 @@ function TagDetailsModalContent(props) { } { - !!artist.length && + artist.length ?
{ artist.map((item) => { @@ -52,11 +53,12 @@ function TagDetailsModalContent(props) { ); }) } -
+ : + null } { - !!delayProfiles.length && + delayProfiles.length ?
{ delayProfiles.map((item) => { @@ -81,11 +83,12 @@ function TagDetailsModalContent(props) { ); }) } -
+ : + null } { - !!notifications.length && + notifications.length ?
{ notifications.map((item) => { @@ -96,11 +99,12 @@ function TagDetailsModalContent(props) { ); }) } -
+ : + null } { - !!importLists.length && + importLists.length ?
{ importLists.map((item) => { @@ -111,11 +115,12 @@ function TagDetailsModalContent(props) { ); }) } -
+ : + null } { - !!releaseProfiles.length && + releaseProfiles.length ?
{ releaseProfiles.map((item) => { @@ -157,7 +162,24 @@ function TagDetailsModalContent(props) { ); }) } -
+ : + null + } + + { + indexers.length ? +
+ { + indexers.map((item) => { + return ( +
+ {item.name} +
+ ); + }) + } +
: + null } @@ -192,6 +214,7 @@ TagDetailsModalContent.propTypes = { importLists: PropTypes.arrayOf(PropTypes.object).isRequired, notifications: PropTypes.arrayOf(PropTypes.object).isRequired, releaseProfiles: PropTypes.arrayOf(PropTypes.object).isRequired, + indexers: PropTypes.arrayOf(PropTypes.object).isRequired, onModalClose: PropTypes.func.isRequired, onDeleteTagPress: PropTypes.func.isRequired }; diff --git a/frontend/src/Settings/Tags/Details/TagDetailsModalContentConnector.js b/frontend/src/Settings/Tags/Details/TagDetailsModalContentConnector.js index 39c68d5f9..5a92c89ed 100644 --- a/frontend/src/Settings/Tags/Details/TagDetailsModalContentConnector.js +++ b/frontend/src/Settings/Tags/Details/TagDetailsModalContentConnector.js @@ -69,6 +69,14 @@ function createMatchingReleaseProfilesSelector() { ); } +function createMatchingIndexersSelector() { + return createSelector( + (state, { indexerIds }) => indexerIds, + (state) => state.settings.indexers.items, + findMatchingItems + ); +} + function createMapStateToProps() { return createSelector( createMatchingArtistSelector(), @@ -76,13 +84,15 @@ function createMapStateToProps() { createMatchingImportListsSelector(), createMatchingNotificationsSelector(), createMatchingReleaseProfilesSelector(), - (artist, delayProfiles, importLists, notifications, releaseProfiles) => { + createMatchingIndexersSelector(), + (artist, delayProfiles, importLists, notifications, releaseProfiles, indexers) => { return { artist, delayProfiles, importLists, notifications, - releaseProfiles + releaseProfiles, + indexers }; } ); diff --git a/frontend/src/Settings/Tags/Tag.js b/frontend/src/Settings/Tags/Tag.js index a73175ed2..983cb6637 100644 --- a/frontend/src/Settings/Tags/Tag.js +++ b/frontend/src/Settings/Tags/Tag.js @@ -57,6 +57,7 @@ class Tag extends Component { importListIds, notificationIds, restrictionIds, + indexerIds, artistIds } = this.props; @@ -70,6 +71,7 @@ class Tag extends Component { importListIds.length || notificationIds.length || restrictionIds.length || + indexerIds.length || artistIds.length ); @@ -87,38 +89,50 @@ class Tag extends Component { isTagUsed &&
{ - !!artistIds.length && + artistIds.length ?
{artistIds.length} artists -
+
: + null } { - !!delayProfileIds.length && + delayProfileIds.length ?
{delayProfileIds.length} delay profile{delayProfileIds.length > 1 && 's'} -
+ : + null } { - !!importListIds.length && + importListIds.length ?
{importListIds.length} import list{importListIds.length > 1 && 's'} -
+ : + null } { - !!notificationIds.length && + notificationIds.length ?
{notificationIds.length} connection{notificationIds.length > 1 && 's'} -
+ : + null } { - !!restrictionIds.length && + restrictionIds.length ?
{restrictionIds.length} restriction{restrictionIds.length > 1 && 's'} -
+ : + null + } + { + indexerIds.length ? +
+ {indexerIds.length} indexer{indexerIds.length > 1 && 's'} +
: + null } } @@ -138,6 +152,7 @@ class Tag extends Component { importListIds={importListIds} notificationIds={notificationIds} restrictionIds={restrictionIds} + indexerIds={indexerIds} isOpen={isDetailsModalOpen} onModalClose={this.onDetailsModalClose} onDeleteTagPress={this.onDeleteTagPress} @@ -164,6 +179,7 @@ Tag.propTypes = { importListIds: PropTypes.arrayOf(PropTypes.number).isRequired, notificationIds: PropTypes.arrayOf(PropTypes.number).isRequired, restrictionIds: PropTypes.arrayOf(PropTypes.number).isRequired, + indexerIds: PropTypes.arrayOf(PropTypes.number).isRequired, artistIds: PropTypes.arrayOf(PropTypes.number).isRequired, onConfirmDeleteTag: PropTypes.func.isRequired }; @@ -173,6 +189,7 @@ Tag.defaultProps = { importListIds: [], notificationIds: [], restrictionIds: [], + indexerIds: [], artistIds: [] }; diff --git a/frontend/src/Settings/Tags/TagsConnector.js b/frontend/src/Settings/Tags/TagsConnector.js index bbfa5d27e..241ee260a 100644 --- a/frontend/src/Settings/Tags/TagsConnector.js +++ b/frontend/src/Settings/Tags/TagsConnector.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { fetchDelayProfiles, fetchImportLists, fetchNotifications, fetchReleaseProfiles } from 'Store/Actions/settingsActions'; +import { fetchDelayProfiles, fetchImportLists, fetchIndexers, fetchNotifications, fetchReleaseProfiles } from 'Store/Actions/settingsActions'; import { fetchTagDetails } from 'Store/Actions/tagActions'; import Tags from './Tags'; @@ -29,7 +29,8 @@ const mapDispatchToProps = { dispatchFetchDelayProfiles: fetchDelayProfiles, dispatchFetchImportLists: fetchImportLists, dispatchFetchNotifications: fetchNotifications, - dispatchFetchReleaseProfiles: fetchReleaseProfiles + dispatchFetchReleaseProfiles: fetchReleaseProfiles, + dispatchFetchIndexers: fetchIndexers }; class MetadatasConnector extends Component { @@ -43,7 +44,8 @@ class MetadatasConnector extends Component { dispatchFetchDelayProfiles, dispatchFetchImportLists, dispatchFetchNotifications, - dispatchFetchReleaseProfiles + dispatchFetchReleaseProfiles, + dispatchFetchIndexers } = this.props; dispatchFetchTagDetails(); @@ -51,6 +53,7 @@ class MetadatasConnector extends Component { dispatchFetchImportLists(); dispatchFetchNotifications(); dispatchFetchReleaseProfiles(); + dispatchFetchIndexers(); } // @@ -70,7 +73,8 @@ MetadatasConnector.propTypes = { dispatchFetchDelayProfiles: PropTypes.func.isRequired, dispatchFetchImportLists: PropTypes.func.isRequired, dispatchFetchNotifications: PropTypes.func.isRequired, - dispatchFetchReleaseProfiles: PropTypes.func.isRequired + dispatchFetchReleaseProfiles: PropTypes.func.isRequired, + dispatchFetchIndexers: PropTypes.func.isRequired }; export default connect(createMapStateToProps, mapDispatchToProps)(MetadatasConnector); diff --git a/src/Lidarr.Api.V1/Tags/TagDetailsResource.cs b/src/Lidarr.Api.V1/Tags/TagDetailsResource.cs index c22b4e07e..1f59e47b0 100644 --- a/src/Lidarr.Api.V1/Tags/TagDetailsResource.cs +++ b/src/Lidarr.Api.V1/Tags/TagDetailsResource.cs @@ -12,6 +12,7 @@ namespace Lidarr.Api.V1.Tags public List ImportListIds { get; set; } public List NotificationIds { get; set; } public List RestrictionIds { get; set; } + public List IndexerIds { get; set; } public List ArtistIds { get; set; } } @@ -32,6 +33,7 @@ namespace Lidarr.Api.V1.Tags ImportListIds = model.ImportListIds, NotificationIds = model.NotificationIds, RestrictionIds = model.RestrictionIds, + IndexerIds = model.IndexerIds, ArtistIds = model.ArtistIds }; } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/IndexerTagSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/IndexerTagSpecificationFixture.cs new file mode 100644 index 000000000..135409b88 --- /dev/null +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/IndexerTagSpecificationFixture.cs @@ -0,0 +1,136 @@ +using System.Collections.Generic; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.DecisionEngine.Specifications.RssSync; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Music; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync +{ + [TestFixture] + public class IndexerTagSpecificationFixture : CoreTest + { + private IndexerTagSpecification _specification; + + private RemoteAlbum _parseResultMulti; + private IndexerDefinition _fakeIndexerDefinition; + private Artist _fakeArtist; + private Album _firstAlbum; + private Album _secondAlbum; + private ReleaseInfo _fakeRelease; + + [SetUp] + public void Setup() + { + _fakeIndexerDefinition = new IndexerDefinition + { + Tags = new HashSet() + }; + + Mocker + .GetMock() + .Setup(m => m.Get(It.IsAny())) + .Throws(new ModelNotFoundException(typeof(IndexerDefinition), -1)); + + Mocker + .GetMock() + .Setup(m => m.Get(1)) + .Returns(_fakeIndexerDefinition); + + _specification = Mocker.Resolve(); + + _fakeArtist = Builder.CreateNew() + .With(c => c.Monitored = true) + .With(c => c.Tags = new HashSet()) + .Build(); + + _fakeRelease = new ReleaseInfo + { + IndexerId = 1 + }; + + _firstAlbum = new Album { Monitored = true }; + _secondAlbum = new Album { Monitored = true }; + + var doubleEpisodeList = new List { _firstAlbum, _secondAlbum }; + + _parseResultMulti = new RemoteAlbum + { + Artist = _fakeArtist, + Albums = doubleEpisodeList, + Release = _fakeRelease + }; + } + + [Test] + public void indexer_and_series_without_tags_should_return_true() + { + _fakeIndexerDefinition.Tags = new HashSet(); + _fakeArtist.Tags = new HashSet(); + + _specification.IsSatisfiedBy(_parseResultMulti, new AlbumSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeTrue(); + } + + [Test] + public void indexer_with_tags_series_without_tags_should_return_false() + { + _fakeIndexerDefinition.Tags = new HashSet { 123 }; + _fakeArtist.Tags = new HashSet(); + + _specification.IsSatisfiedBy(_parseResultMulti, new AlbumSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeFalse(); + } + + [Test] + public void indexer_without_tags_series_with_tags_should_return_true() + { + _fakeIndexerDefinition.Tags = new HashSet(); + _fakeArtist.Tags = new HashSet { 123 }; + + _specification.IsSatisfiedBy(_parseResultMulti, new AlbumSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeTrue(); + } + + [Test] + public void indexer_with_tags_series_with_matching_tags_should_return_true() + { + _fakeIndexerDefinition.Tags = new HashSet { 123, 456 }; + _fakeArtist.Tags = new HashSet { 123, 789 }; + + _specification.IsSatisfiedBy(_parseResultMulti, new AlbumSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeTrue(); + } + + [Test] + public void indexer_with_tags_series_with_different_tags_should_return_false() + { + _fakeIndexerDefinition.Tags = new HashSet { 456 }; + _fakeArtist.Tags = new HashSet { 123, 789 }; + + _specification.IsSatisfiedBy(_parseResultMulti, new AlbumSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeFalse(); + } + + [Test] + public void release_without_indexerid_should_return_true() + { + _fakeIndexerDefinition.Tags = new HashSet { 456 }; + _fakeArtist.Tags = new HashSet { 123, 789 }; + _fakeRelease.IndexerId = 0; + + _specification.IsSatisfiedBy(_parseResultMulti, new AlbumSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeTrue(); + } + + [Test] + public void release_with_invalid_indexerid_should_return_true() + { + _fakeIndexerDefinition.Tags = new HashSet { 456 }; + _fakeArtist.Tags = new HashSet { 123, 789 }; + _fakeRelease.IndexerId = 2; + + _specification.IsSatisfiedBy(_parseResultMulti, new AlbumSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeTrue(); + } + } +} diff --git a/src/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs index 4cc12f77b..4ade676d9 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs @@ -1,10 +1,10 @@ -using System.Collections.Generic; +using System.Collections.Generic; using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers.FileList; using NzbDrone.Core.Indexers.Newznab; -using NzbDrone.Core.Indexers.Omgwtfnzbs; using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Test.Framework; @@ -20,7 +20,7 @@ namespace NzbDrone.Core.Test.IndexerTests _indexers = new List(); _indexers.Add(Mocker.Resolve()); - _indexers.Add(Mocker.Resolve()); + _indexers.Add(Mocker.Resolve()); Mocker.SetConstant>(_indexers); } diff --git a/src/NzbDrone.Core.Test/IndexerTests/OmgwtfnzbsTests/OmgwtfnzbsFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/OmgwtfnzbsTests/OmgwtfnzbsFixture.cs deleted file mode 100644 index 1b19fd7f1..000000000 --- a/src/NzbDrone.Core.Test/IndexerTests/OmgwtfnzbsTests/OmgwtfnzbsFixture.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Linq; -using FluentAssertions; -using Moq; -using NUnit.Framework; -using NzbDrone.Common.Http; -using NzbDrone.Core.Indexers; -using NzbDrone.Core.Indexers.Omgwtfnzbs; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.IndexerTests.OmgwtfnzbsTests -{ - [TestFixture] - public class OmgwtfnzbsFixture : CoreTest - { - [SetUp] - public void Setup() - { - Subject.Definition = new IndexerDefinition() - { - Name = "Omgwtfnzbs", - Settings = new OmgwtfnzbsSettings() - { - ApiKey = "xxx", - Username = "me@my.domain" - } - }; - } - - [Test] - public void should_parse_recent_feed_from_omgwtfnzbs() - { - var recentFeed = ReadAllText(@"Files/Indexers/Omgwtfnzbs/Omgwtfnzbs.xml"); - - Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) - .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); - - var releases = Subject.FetchRecent(); - - releases.Should().HaveCount(100); - - var releaseInfo = releases.First(); - - releaseInfo.Title.Should().Be("Stephen.Fry.Gadget.Man.S01E05.HDTV.x264-C4TV"); - releaseInfo.DownloadProtocol.Should().Be(DownloadProtocol.Usenet); - releaseInfo.DownloadUrl.Should().Be("http://api.omgwtfnzbs.org/sn.php?id=OAl4g&user=nzbdrone&api=nzbdrone"); - releaseInfo.InfoUrl.Should().Be("http://omgwtfnzbs.org/details.php?id=OAl4g"); - releaseInfo.CommentUrl.Should().BeNullOrEmpty(); - releaseInfo.Indexer.Should().Be(Subject.Definition.Name); - releaseInfo.PublishDate.Should().Be(DateTime.Parse("2012/12/17 23:30:13")); - releaseInfo.Size.Should().Be(236822906); - } - } -} diff --git a/src/NzbDrone.Core.Test/IndexerTests/WafflesTests/WafflesFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/WafflesTests/WafflesFixture.cs deleted file mode 100644 index eb313b1ef..000000000 --- a/src/NzbDrone.Core.Test/IndexerTests/WafflesTests/WafflesFixture.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Linq; -using FluentAssertions; -using Moq; -using NUnit.Framework; -using NzbDrone.Common.Http; -using NzbDrone.Core.Indexers; -using NzbDrone.Core.Indexers.Waffles; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.IndexerTests.WafflesTests -{ - [TestFixture] - public class WafflesFixture : CoreTest - { - [SetUp] - public void Setup() - { - Subject.Definition = new IndexerDefinition() - { - Name = "Waffles", - Settings = new WafflesSettings() - { - UserId = "xxx", - RssPasskey = "123456789" - } - }; - } - - [Test] - public void should_parse_recent_feed_from_waffles() - { - var recentFeed = ReadAllText(@"Files/Indexers/Waffles/waffles.xml"); - - Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) - .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); - - var releases = Subject.FetchRecent(); - - releases.Should().HaveCount(15); - - var releaseInfo = releases.First(); - - releaseInfo.Title.Should().Be("Coldplay - Kaleidoscope EP (FLAC HD) [2017-Web-FLAC-Lossless]"); - releaseInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent); - releaseInfo.DownloadUrl.Should().Be("https://waffles.ch/download.php/xxx/1166992/" + - "Coldplay%20-%20Kaleidoscope%20EP%20%28FLAC%20HD%29%20%5B2017-Web-FLAC-Lossless%5D.torrent?passkey=123456789&uid=xxx&rss=1"); - releaseInfo.InfoUrl.Should().Be("https://waffles.ch/details.php?id=1166992&hit=1"); - releaseInfo.CommentUrl.Should().Be("https://waffles.ch/details.php?id=1166992&hit=1"); - releaseInfo.Indexer.Should().Be(Subject.Definition.Name); - releaseInfo.PublishDate.Should().Be(DateTime.Parse("2017-07-16 09:51:54")); - releaseInfo.Size.Should().Be(552668227); - } - } -} diff --git a/src/NzbDrone.Core/Datastore/Migration/059_indexer_tags.cs b/src/NzbDrone.Core/Datastore/Migration/059_indexer_tags.cs new file mode 100644 index 000000000..e45340a3c --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/059_indexer_tags.cs @@ -0,0 +1,17 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(059)] + public class add_indexer_tags : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Execute.Sql("DELETE FROM Indexers WHERE Implementation = 'Omgwtfnzbs'"); + Execute.Sql("DELETE FROM Indexers WHERE Implementation = 'Waffles'"); + + Alter.Table("Indexers").AddColumn("Tags").AsString().Nullable(); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index f1554484d..096ec53b1 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -68,8 +68,7 @@ namespace NzbDrone.Core.Datastore .Ignore(i => i.Enable) .Ignore(i => i.Protocol) .Ignore(i => i.SupportsRss) - .Ignore(i => i.SupportsSearch) - .Ignore(d => d.Tags); + .Ignore(i => i.SupportsSearch); Mapper.Entity("ImportLists").RegisterModel() .Ignore(x => x.ImplementationName) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/IndexerTagSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/IndexerTagSpecification.cs new file mode 100644 index 000000000..68d6b0277 --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/IndexerTagSpecification.cs @@ -0,0 +1,56 @@ +using System.Linq; +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync +{ + public class IndexerTagSpecification : IDecisionEngineSpecification + { + private readonly Logger _logger; + private readonly IIndexerFactory _indexerFactory; + + public IndexerTagSpecification(Logger logger, IIndexerFactory indexerFactory) + { + _logger = logger; + _indexerFactory = indexerFactory; + } + + public SpecificationPriority Priority => SpecificationPriority.Default; + public RejectionType Type => RejectionType.Permanent; + + public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) + { + if (subject.Release == null || subject.Artist?.Tags == null || subject.Release.IndexerId == 0) + { + return Decision.Accept(); + } + + IndexerDefinition indexer; + try + { + indexer = _indexerFactory.Get(subject.Release.IndexerId); + } + catch (ModelNotFoundException) + { + _logger.Debug("Indexer with id {0} does not exist, skipping indexer tags check", subject.Release.IndexerId); + return Decision.Accept(); + } + + // If indexer has tags, check that at least one of them is present on the series + var indexerTags = indexer.Tags; + + if (indexerTags.Any() && indexerTags.Intersect(subject.Artist.Tags).Empty()) + { + _logger.Debug("Indexer {0} has tags. None of these are present on artist {1}. Rejecting", subject.Release.Indexer, subject.Artist); + + return Decision.Reject("Artist tags do not match any of the indexer tags"); + } + + return Decision.Accept(); + } + } +} diff --git a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs index 89ff4e779..a3c3644af 100644 --- a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs @@ -125,6 +125,9 @@ namespace NzbDrone.Core.IndexerSearch _indexerFactory.InteractiveSearchEnabled() : _indexerFactory.AutomaticSearchEnabled(); + // Filter indexers to untagged indexers and indexers with intersecting tags + indexers = indexers.Where(i => i.Definition.Tags.Empty() || i.Definition.Tags.Intersect(criteriaBase.Artist.Tags).Any()).ToList(); + var reports = new List(); _logger.ProgressInfo("Searching indexers for {0}. {1} active indexers", criteriaBase, indexers.Count); diff --git a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs index a0109a5fe..5e49f5b18 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs @@ -47,7 +47,6 @@ namespace NzbDrone.Core.Indexers.Newznab yield return GetDefinition("NZBFinder.ws", GetSettings("https://nzbfinder.ws")); yield return GetDefinition("NZBgeek", GetSettings("https://api.nzbgeek.info")); yield return GetDefinition("nzbplanet.net", GetSettings("https://api.nzbplanet.net")); - yield return GetDefinition("omgwtfnzbs", GetSettings("https://api.omgwtfnzbs.me")); yield return GetDefinition("OZnzb.com", GetSettings("https://api.oznzb.com")); yield return GetDefinition("SimplyNZBs", GetSettings("https://simplynzbs.com")); yield return GetDefinition("Tabula Rasa", GetSettings("https://www.tabula-rasa.pw", apiPath: @"/api/v1/api")); diff --git a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs deleted file mode 100644 index e4f1bb56b..000000000 --- a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs +++ /dev/null @@ -1,29 +0,0 @@ -using NLog; -using NzbDrone.Common.Http; -using NzbDrone.Core.Configuration; -using NzbDrone.Core.Parser; - -namespace NzbDrone.Core.Indexers.Omgwtfnzbs -{ - public class Omgwtfnzbs : HttpIndexerBase - { - public override string Name => "omgwtfnzbs"; - - public override DownloadProtocol Protocol => DownloadProtocol.Usenet; - - public Omgwtfnzbs(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) - : base(httpClient, indexerStatusService, configService, parsingService, logger) - { - } - - public override IIndexerRequestGenerator GetRequestGenerator() - { - return new OmgwtfnzbsRequestGenerator() { Settings = Settings }; - } - - public override IParseIndexerResponse GetParser() - { - return new OmgwtfnzbsRssParser(); - } - } -} diff --git a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsRequestGenerator.cs deleted file mode 100644 index 80e73e276..000000000 --- a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsRequestGenerator.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Collections.Generic; -using System.Text; -using NzbDrone.Common.Extensions; -using NzbDrone.Common.Http; -using NzbDrone.Core.IndexerSearch.Definitions; - -namespace NzbDrone.Core.Indexers.Omgwtfnzbs -{ - public class OmgwtfnzbsRequestGenerator : IIndexerRequestGenerator - { - public string BaseUrl { get; set; } - public OmgwtfnzbsSettings Settings { get; set; } - - public OmgwtfnzbsRequestGenerator() - { - BaseUrl = "https://rss.omgwtfnzbs.me/rss-download.php"; - } - - public virtual IndexerPageableRequestChain GetRecentRequests() - { - var pageableRequests = new IndexerPageableRequestChain(); - - pageableRequests.Add(GetPagedRequests(null)); - - return pageableRequests; - } - - public virtual IndexerPageableRequestChain GetSearchRequests(AlbumSearchCriteria searchCriteria) - { - var pageableRequests = new IndexerPageableRequestChain(); - - pageableRequests.Add(GetPagedRequests(string.Format("{0}+{1}", - searchCriteria.ArtistQuery, - searchCriteria.AlbumQuery))); - - return pageableRequests; - } - - public virtual IndexerPageableRequestChain GetSearchRequests(ArtistSearchCriteria searchCriteria) - { - var pageableRequests = new IndexerPageableRequestChain(); - - pageableRequests.Add(GetPagedRequests(string.Format("{0}", - searchCriteria.ArtistQuery))); - - return pageableRequests; - } - - private IEnumerable GetPagedRequests(string query) - { - var url = new StringBuilder(); - - // Category 22 is Music-FLAC, category 7 is Music-MP3 - url.AppendFormat("{0}?catid=22,7&user={1}&api={2}&eng=1&delay={3}", BaseUrl, Settings.Username, Settings.ApiKey, Settings.Delay); - - if (query.IsNotNullOrWhiteSpace()) - { - url = url.Replace("rss-download.php", "rss-search.php"); - url.AppendFormat("&search={0}", query); - } - - yield return new IndexerRequest(url.ToString(), HttpAccept.Rss); - } - } -} diff --git a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsRssParser.cs b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsRssParser.cs deleted file mode 100644 index 5ff9174e3..000000000 --- a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsRssParser.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Linq; -using System.Text.RegularExpressions; -using System.Xml.Linq; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.Indexers.Exceptions; - -namespace NzbDrone.Core.Indexers.Omgwtfnzbs -{ - public class OmgwtfnzbsRssParser : RssParser - { - public OmgwtfnzbsRssParser() - { - UseEnclosureUrl = true; - UseEnclosureLength = true; - } - - protected override bool PreProcess(IndexerResponse indexerResponse) - { - var xdoc = LoadXmlDocument(indexerResponse); - var notice = xdoc.Descendants("notice").FirstOrDefault(); - - if (notice == null) - { - return true; - } - - if (!notice.Value.ContainsIgnoreCase("api")) - { - return true; - } - - throw new ApiKeyException(notice.Value); - } - - protected override string GetInfoUrl(XElement item) - { - //Todo: Me thinks I need to parse details to get this... - var match = Regex.Match(item.Description(), - @"(?:\View NZB\:\<\/b\>\s\.+)(?:\""\starget)", - RegexOptions.IgnoreCase | RegexOptions.Compiled); - - if (match.Success) - { - return match.Groups["URL"].Value; - } - - return string.Empty; - } - } -} diff --git a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsSettings.cs b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsSettings.cs deleted file mode 100644 index ff5eeb02e..000000000 --- a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsSettings.cs +++ /dev/null @@ -1,46 +0,0 @@ -using FluentValidation; -using NzbDrone.Core.Annotations; -using NzbDrone.Core.Validation; - -namespace NzbDrone.Core.Indexers.Omgwtfnzbs -{ - public class OmgwtfnzbsSettingsValidator : AbstractValidator - { - public OmgwtfnzbsSettingsValidator() - { - RuleFor(c => c.Username).NotEmpty(); - RuleFor(c => c.ApiKey).NotEmpty(); - RuleFor(c => c.Delay).GreaterThanOrEqualTo(0); - } - } - - public class OmgwtfnzbsSettings : IIndexerSettings - { - private static readonly OmgwtfnzbsSettingsValidator Validator = new OmgwtfnzbsSettingsValidator(); - - public OmgwtfnzbsSettings() - { - Delay = 30; - } - - // Unused since Omg has a hardcoded url. - public string BaseUrl { get; set; } - - [FieldDefinition(0, Label = "Username", Privacy = PrivacyLevel.UserName)] - public string Username { get; set; } - - [FieldDefinition(1, Label = "API Key", Privacy = PrivacyLevel.ApiKey)] - public string ApiKey { get; set; } - - [FieldDefinition(2, Label = "Delay", HelpText = "Time in minutes to delay new nzbs before they appear on the RSS feed", Advanced = true)] - public int Delay { get; set; } - - [FieldDefinition(3, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)] - public int? EarlyReleaseLimit { get; set; } - - public NzbDroneValidationResult Validate() - { - return new NzbDroneValidationResult(Validator.Validate(this)); - } - } -} diff --git a/src/NzbDrone.Core/Indexers/Waffles/Waffles.cs b/src/NzbDrone.Core/Indexers/Waffles/Waffles.cs deleted file mode 100644 index 9f2377fb2..000000000 --- a/src/NzbDrone.Core/Indexers/Waffles/Waffles.cs +++ /dev/null @@ -1,30 +0,0 @@ -using NLog; -using NzbDrone.Common.Http; -using NzbDrone.Core.Configuration; -using NzbDrone.Core.Parser; - -namespace NzbDrone.Core.Indexers.Waffles -{ - public class Waffles : HttpIndexerBase - { - public override string Name => "Waffles"; - - public override DownloadProtocol Protocol => DownloadProtocol.Torrent; - public override int PageSize => 15; - - public Waffles(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) - : base(httpClient, indexerStatusService, configService, parsingService, logger) - { - } - - public override IIndexerRequestGenerator GetRequestGenerator() - { - return new WafflesRequestGenerator() { Settings = Settings }; - } - - public override IParseIndexerResponse GetParser() - { - return new WafflesRssParser() { ParseSizeInDescription = true, ParseSeedersInDescription = true }; - } - } -} diff --git a/src/NzbDrone.Core/Indexers/Waffles/WafflesRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Waffles/WafflesRequestGenerator.cs deleted file mode 100644 index fa39d562b..000000000 --- a/src/NzbDrone.Core/Indexers/Waffles/WafflesRequestGenerator.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Collections.Generic; -using System.Text; -using NzbDrone.Common.Extensions; -using NzbDrone.Common.Http; -using NzbDrone.Core.IndexerSearch.Definitions; - -namespace NzbDrone.Core.Indexers.Waffles -{ - public class WafflesRequestGenerator : IIndexerRequestGenerator - { - public WafflesSettings Settings { get; set; } - public int MaxPages { get; set; } - - public WafflesRequestGenerator() - { - MaxPages = 5; - } - - public virtual IndexerPageableRequestChain GetRecentRequests() - { - var pageableRequests = new IndexerPageableRequestChain(); - - pageableRequests.Add(GetPagedRequests(MaxPages, null)); - - return pageableRequests; - } - - public IndexerPageableRequestChain GetSearchRequests(AlbumSearchCriteria searchCriteria) - { - var pageableRequests = new IndexerPageableRequestChain(); - - pageableRequests.Add(GetPagedRequests(MaxPages, string.Format("&q=artist:{0} album:{1}", searchCriteria.ArtistQuery, searchCriteria.AlbumQuery))); - - return pageableRequests; - } - - public IndexerPageableRequestChain GetSearchRequests(ArtistSearchCriteria searchCriteria) - { - var pageableRequests = new IndexerPageableRequestChain(); - - pageableRequests.Add(GetPagedRequests(MaxPages, string.Format("&q=artist:{0}", searchCriteria.ArtistQuery))); - - return pageableRequests; - } - - private IEnumerable GetPagedRequests(int maxPages, string query) - { - var url = new StringBuilder(); - - url.AppendFormat("{0}/browse.php?rss=1&c0=1&uid={1}&passkey={2}", Settings.BaseUrl.Trim().TrimEnd('/'), Settings.UserId, Settings.RssPasskey); - - if (query.IsNotNullOrWhiteSpace()) - { - url.AppendFormat(query); - } - - for (var page = 0; page < maxPages; page++) - { - yield return new IndexerRequest(string.Format("{0}&p={1}", url, page), HttpAccept.Rss); - } - } - } -} diff --git a/src/NzbDrone.Core/Indexers/Waffles/WafflesRssParser.cs b/src/NzbDrone.Core/Indexers/Waffles/WafflesRssParser.cs deleted file mode 100644 index a5744d141..000000000 --- a/src/NzbDrone.Core/Indexers/Waffles/WafflesRssParser.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Text.RegularExpressions; -using System.Xml.Linq; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.Indexers.Exceptions; -using NzbDrone.Core.Parser.Model; - -namespace NzbDrone.Core.Indexers.Waffles -{ - public class WafflesRssParser : TorrentRssParser - { - public const string ns = "{http://purl.org/rss/1.0/}"; - public const string dc = "{http://purl.org/dc/elements/1.1/}"; - - protected override bool PreProcess(IndexerResponse indexerResponse) - { - var xdoc = LoadXmlDocument(indexerResponse); - var error = xdoc.Descendants("error").FirstOrDefault(); - - if (error == null) - { - return true; - } - - var code = Convert.ToInt32(error.Attribute("code").Value); - var errorMessage = error.Attribute("description").Value; - - if (code >= 100 && code <= 199) - { - throw new ApiKeyException("Invalid Pass key"); - } - - if (!indexerResponse.Request.Url.FullUri.Contains("passkey=") && errorMessage == "Missing parameter") - { - throw new ApiKeyException("Indexer requires an Pass key"); - } - - if (errorMessage == "Request limit reached") - { - throw new RequestLimitReachedException("API limit reached"); - } - - throw new IndexerException(indexerResponse, errorMessage); - } - - protected override ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInfo) - { - var torrentInfo = base.ProcessItem(item, releaseInfo) as TorrentInfo; - - return torrentInfo; - } - - protected override string GetInfoUrl(XElement item) - { - return ParseUrl(item.TryGetValue("comments").TrimEnd("#comments")); - } - - protected override string GetCommentUrl(XElement item) - { - return ParseUrl(item.TryGetValue("comments")); - } - - private static readonly Regex ParseSizeRegex = new Regex(@"(?:Size: )(?\d+)<", - RegexOptions.IgnoreCase | RegexOptions.Compiled); - - protected override long GetSize(XElement item) - { - var match = ParseSizeRegex.Matches(item.Element("description").Value); - - if (match.Count != 0) - { - var value = decimal.Parse(Regex.Replace(match[0].Groups["value"].Value, "\\,", ""), CultureInfo.InvariantCulture); - return (long)value; - } - - return 0; - } - - protected override DateTime GetPublishDate(XElement item) - { - var dateString = item.TryGetValue(dc + "date"); - - if (dateString.IsNullOrWhiteSpace()) - { - throw new UnsupportedFeedException("Rss feed must have a pubDate element with a valid publish date."); - } - - return XElementExtensions.ParseDate(dateString); - } - } -} diff --git a/src/NzbDrone.Core/Indexers/Waffles/WafflesSettings.cs b/src/NzbDrone.Core/Indexers/Waffles/WafflesSettings.cs deleted file mode 100644 index 410cc204c..000000000 --- a/src/NzbDrone.Core/Indexers/Waffles/WafflesSettings.cs +++ /dev/null @@ -1,50 +0,0 @@ -using FluentValidation; -using NzbDrone.Core.Annotations; -using NzbDrone.Core.Validation; - -namespace NzbDrone.Core.Indexers.Waffles -{ - public class WafflesSettingsValidator : AbstractValidator - { - public WafflesSettingsValidator() - { - RuleFor(c => c.BaseUrl).ValidRootUrl(); - RuleFor(c => c.UserId).NotEmpty(); - RuleFor(c => c.RssPasskey).NotEmpty(); - } - } - - public class WafflesSettings : ITorrentIndexerSettings - { - private static readonly WafflesSettingsValidator Validator = new WafflesSettingsValidator(); - - public WafflesSettings() - { - BaseUrl = "https://www.waffles.ch"; - MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; - } - - [FieldDefinition(0, Label = "Website URL")] - public string BaseUrl { get; set; } - - [FieldDefinition(1, Label = "UserId", Privacy = PrivacyLevel.UserName)] - public string UserId { get; set; } - - [FieldDefinition(2, Label = "RSS Passkey", Privacy = PrivacyLevel.ApiKey)] - public string RssPasskey { get; set; } - - [FieldDefinition(3, Type = FieldType.Textbox, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)] - public int MinimumSeeders { get; set; } - - [FieldDefinition(4)] - public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings(); - - [FieldDefinition(5, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)] - public int? EarlyReleaseLimit { get; set; } - - public NzbDroneValidationResult Validate() - { - return new NzbDroneValidationResult(Validator.Validate(this)); - } - } -} diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index ca43a4485..230ce6453 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -269,6 +269,7 @@ "IncludeUnknownArtistItemsHelpText": "Show items without a artist in the queue, this could include removed artists, movies or anything else in Lidarr's category", "IncludeUnmonitored": "Include Unmonitored", "Indexer": "Indexer", + "IndexerDownloadClientHelpText": "Specify which download client is used for grabs from this indexer", "IndexerIdHelpText": "Specify what indexer the profile applies to", "IndexerIdHelpTextWarning": "Using a specific indexer with preferred words can lead to duplicate releases being grabbed", "IndexerIdvalue0IncludeInPreferredWordsRenamingFormat": "Include in {Preferred Words} renaming format", @@ -276,6 +277,7 @@ "IndexerPriority": "Indexer Priority", "Indexers": "Indexers", "IndexerSettings": "Indexer Settings", + "IndexerTagHelpText": "Only use this indexer for artist with at least one matching tag. Leave blank to use with all artists.", "InstanceName": "Instance Name", "InstanceNameHelpText": "Instance name in tab and for Syslog app name", "InteractiveSearch": "Interactive Search", @@ -321,8 +323,8 @@ "ManualImport": "Manual Import", "MarkAsFailed": "Mark as Failed", "MarkAsFailedMessageText": "Are you sure you want to mark '{0}' as failed?", - "MassAlbumsSearchWarning": "Are you sure you want to search for all '{0}' missing albums?", "MassAlbumsCutoffUnmetWarning": "Are you sure you want to search for all '{0}' Cutoff Unmet albums?", + "MassAlbumsSearchWarning": "Are you sure you want to search for all '{0}' missing albums?", "MaximumLimits": "Maximum Limits", "MaximumSize": "Maximum Size", "MaximumSizeHelpText": "Maximum size for a release to be grabbed in MB. Set to zero to set to unlimited.", @@ -550,9 +552,9 @@ "SetPermissionsLinuxHelpTextWarning": "If you're unsure what these settings do, do not alter them.", "Settings": "Settings", "ShortDateFormat": "Short Date Format", - "ShouldMonitorHelpText": "Monitor artists and albums added from this list", "ShouldMonitorExisting": "Monitor existing albums", "ShouldMonitorExistingHelpText": "Automatically monitor albums on this list which are already in Lidarr", + "ShouldMonitorHelpText": "Monitor artists and albums added from this list", "ShouldSearch": "Search for New Items", "ShouldSearchHelpText": "Search indexers for newly added items. Use with caution for large lists.", "ShowAlbumCount": "Show Album Count", diff --git a/src/NzbDrone.Core/Tags/TagDetails.cs b/src/NzbDrone.Core/Tags/TagDetails.cs index 1730ce760..f11fc802e 100644 --- a/src/NzbDrone.Core/Tags/TagDetails.cs +++ b/src/NzbDrone.Core/Tags/TagDetails.cs @@ -13,12 +13,13 @@ namespace NzbDrone.Core.Tags public List DelayProfileIds { get; set; } public List ImportListIds { get; set; } public List RootFolderIds { get; set; } + public List IndexerIds { get; set; } public bool InUse { get { - return ArtistIds.Any() || NotificationIds.Any() || RestrictionIds.Any() || DelayProfileIds.Any() || ImportListIds.Any() || RootFolderIds.Any(); + return ArtistIds.Any() || NotificationIds.Any() || RestrictionIds.Any() || DelayProfileIds.Any() || ImportListIds.Any() || RootFolderIds.Any() || IndexerIds.Any(); } } } diff --git a/src/NzbDrone.Core/Tags/TagService.cs b/src/NzbDrone.Core/Tags/TagService.cs index 7ef08df3c..328e3f251 100644 --- a/src/NzbDrone.Core/Tags/TagService.cs +++ b/src/NzbDrone.Core/Tags/TagService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using NzbDrone.Core.Datastore; using NzbDrone.Core.ImportLists; +using NzbDrone.Core.Indexers; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Music; using NzbDrone.Core.Notifications; @@ -33,6 +34,7 @@ namespace NzbDrone.Core.Tags private readonly IReleaseProfileService _releaseProfileService; private readonly IArtistService _artistService; private readonly IRootFolderService _rootFolderService; + private readonly IIndexerFactory _indexerService; public TagService(ITagRepository repo, IEventAggregator eventAggregator, @@ -41,7 +43,8 @@ namespace NzbDrone.Core.Tags INotificationFactory notificationFactory, IReleaseProfileService releaseProfileService, IArtistService artistService, - IRootFolderService rootFolderService) + IRootFolderService rootFolderService, + IIndexerFactory indexerService) { _repo = repo; _eventAggregator = eventAggregator; @@ -51,6 +54,7 @@ namespace NzbDrone.Core.Tags _releaseProfileService = releaseProfileService; _artistService = artistService; _rootFolderService = rootFolderService; + _indexerService = indexerService; } public Tag GetTag(int tagId) @@ -79,6 +83,7 @@ namespace NzbDrone.Core.Tags var restrictions = _releaseProfileService.AllForTag(tagId); var artist = _artistService.AllForTag(tagId); var rootFolders = _rootFolderService.AllForTag(tagId); + var indexers = _indexerService.AllForTag(tagId); return new TagDetails { @@ -89,7 +94,8 @@ namespace NzbDrone.Core.Tags NotificationIds = notifications.Select(c => c.Id).ToList(), RestrictionIds = restrictions.Select(c => c.Id).ToList(), ArtistIds = artist.Select(c => c.Id).ToList(), - RootFolderIds = rootFolders.Select(c => c.Id).ToList() + RootFolderIds = rootFolders.Select(c => c.Id).ToList(), + IndexerIds = indexers.Select(c => c.Id).ToList() }; } @@ -102,6 +108,7 @@ namespace NzbDrone.Core.Tags var restrictions = _releaseProfileService.All(); var artists = _artistService.GetAllArtists(); var rootFolders = _rootFolderService.All(); + var indexers = _indexerService.All(); var details = new List(); @@ -116,7 +123,8 @@ namespace NzbDrone.Core.Tags NotificationIds = notifications.Where(c => c.Tags.Contains(tag.Id)).Select(c => c.Id).ToList(), RestrictionIds = restrictions.Where(c => c.Tags.Contains(tag.Id)).Select(c => c.Id).ToList(), ArtistIds = artists.Where(c => c.Tags.Contains(tag.Id)).Select(c => c.Id).ToList(), - RootFolderIds = rootFolders.Where(c => c.DefaultTags.Contains(tag.Id)).Select(c => c.Id).ToList() + RootFolderIds = rootFolders.Where(c => c.DefaultTags.Contains(tag.Id)).Select(c => c.Id).ToList(), + IndexerIds = indexers.Where(c => c.Tags.Contains(tag.Id)).Select(c => c.Id).ToList() }); }