From f0f95be57fb3a313e92a893770699f868ce802b8 Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 10 Jul 2023 22:51:14 -0500 Subject: [PATCH] New: Download Client Tags (cherry picked from commit f6ae9fd6c5173cbf1540341fa99d2f120be1d28e) --- .../DownloadClients/DownloadClient.js | 12 +++- .../DownloadClients/DownloadClients.js | 3 + .../DownloadClientsConnector.js | 9 ++- .../EditDownloadClientModalContent.js | 13 ++++ .../Tags/Details/TagDetailsModalContent.js | 18 +++++ .../TagDetailsModalContentConnector.js | 14 +++- frontend/src/Settings/Tags/Tag.js | 13 ++++ frontend/src/Settings/Tags/TagsConnector.js | 12 ++-- .../034_cdh_per_downloadclientFixture.cs | 2 + .../Download/DownloadClientProviderFixture.cs | 70 ++++++++++++++++++- .../Download/DownloadServiceFixture.cs | 4 +- .../Migration/035_add_download_client_tags.cs | 14 ++++ src/NzbDrone.Core/Datastore/TableMapping.cs | 3 +- .../Download/DownloadClientProvider.cs | 14 +++- src/NzbDrone.Core/Download/DownloadService.cs | 3 +- .../Housekeepers/CleanupUnusedTags.cs | 2 +- src/NzbDrone.Core/Localization/Core/en.json | 1 + src/NzbDrone.Core/Tags/TagDetails.cs | 16 +++-- src/NzbDrone.Core/Tags/TagService.cs | 14 +++- src/Readarr.Api.V1/Tags/TagDetailsResource.cs | 2 + 20 files changed, 210 insertions(+), 29 deletions(-) create mode 100644 src/NzbDrone.Core/Datastore/Migration/035_add_download_client_tags.cs diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClient.js b/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClient.js index 0a19be274..5a86b32d6 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClient.js +++ b/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClient.js @@ -3,6 +3,7 @@ import React, { Component } from 'react'; import Card from 'Components/Card'; import Label from 'Components/Label'; import ConfirmModal from 'Components/Modal/ConfirmModal'; +import TagList from 'Components/TagList'; import { kinds } from 'Helpers/Props'; import translate from 'Utilities/String/translate'; import EditDownloadClientModalConnector from './EditDownloadClientModalConnector'; @@ -56,7 +57,9 @@ class DownloadClient extends Component { id, name, enable, - priority + priority, + tags, + tagList } = this.props; return ( @@ -94,6 +97,11 @@ class DownloadClient extends Component { } + + ); @@ -109,6 +111,7 @@ DownloadClients.propTypes = { isFetching: PropTypes.bool.isRequired, error: PropTypes.object, items: PropTypes.arrayOf(PropTypes.object).isRequired, + tagList: PropTypes.arrayOf(PropTypes.object).isRequired, onConfirmDeleteDownloadClient: PropTypes.func.isRequired }; diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClientsConnector.js b/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClientsConnector.js index 9cba9c1cc..d9e543469 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClientsConnector.js +++ b/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClientsConnector.js @@ -4,13 +4,20 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import { deleteDownloadClient, fetchDownloadClients } from 'Store/Actions/settingsActions'; import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector'; +import createTagsSelector from 'Store/Selectors/createTagsSelector'; import sortByName from 'Utilities/Array/sortByName'; import DownloadClients from './DownloadClients'; function createMapStateToProps() { return createSelector( createSortedSectionSelector('settings.downloadClients', sortByName), - (downloadClients) => downloadClients + createTagsSelector(), + (downloadClients, tagList) => { + return { + ...downloadClients, + tagList + }; + } ); } diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContent.js b/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContent.js index efc894d47..08b966742 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContent.js +++ b/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContent.js @@ -51,6 +51,7 @@ class EditDownloadClientModalContent extends Component { removeCompletedDownloads, removeFailedDownloads, fields, + tags, message } = item; @@ -146,6 +147,18 @@ class EditDownloadClientModalContent extends Component { /> + + {translate('Tags')} + + + +
: null } + + { + downloadClients.length ? +
+ { + downloadClients.map((item) => { + return ( +
+ {item.name} +
+ ); + }) + } +
: + null + } @@ -214,6 +231,7 @@ TagDetailsModalContent.propTypes = { notifications: PropTypes.arrayOf(PropTypes.object).isRequired, releaseProfiles: PropTypes.arrayOf(PropTypes.object).isRequired, indexers: PropTypes.arrayOf(PropTypes.object).isRequired, + downloadClients: 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 64a829d47..e8f8b118d 100644 --- a/frontend/src/Settings/Tags/Details/TagDetailsModalContentConnector.js +++ b/frontend/src/Settings/Tags/Details/TagDetailsModalContentConnector.js @@ -77,6 +77,14 @@ function createMatchingIndexersSelector() { ); } +function createMatchingDownloadClientsSelector() { + return createSelector( + (state, { downloadClientIds }) => downloadClientIds, + (state) => state.settings.downloadClients.items, + findMatchingItems + ); +} + function createMapStateToProps() { return createSelector( createMatchingAuthorSelector(), @@ -85,14 +93,16 @@ function createMapStateToProps() { createMatchingNotificationsSelector(), createMatchingReleaseProfilesSelector(), createMatchingIndexersSelector(), - (author, delayProfiles, importLists, notifications, releaseProfiles, indexers) => { + createMatchingDownloadClientsSelector(), + (author, delayProfiles, importLists, notifications, releaseProfiles, indexers, downloadClients) => { return { author, delayProfiles, importLists, notifications, releaseProfiles, - indexers + indexers, + downloadClients }; } ); diff --git a/frontend/src/Settings/Tags/Tag.js b/frontend/src/Settings/Tags/Tag.js index b68db9519..daee2cdd6 100644 --- a/frontend/src/Settings/Tags/Tag.js +++ b/frontend/src/Settings/Tags/Tag.js @@ -58,6 +58,7 @@ class Tag extends Component { notificationIds, restrictionIds, indexerIds, + downloadClientIds, authorIds } = this.props; @@ -72,6 +73,7 @@ class Tag extends Component { notificationIds.length || restrictionIds.length || indexerIds.length || + downloadClientIds.length || authorIds.length ); @@ -130,6 +132,14 @@ class Tag extends Component { : null } + + { + downloadClientIds.length ? +
+ {downloadClientIds.length} download client{indexerIds.length > 1 && 's'} +
: + null + } } @@ -149,6 +159,7 @@ class Tag extends Component { notificationIds={notificationIds} restrictionIds={restrictionIds} indexerIds={indexerIds} + downloadClientIds={downloadClientIds} isOpen={isDetailsModalOpen} onModalClose={this.onDetailsModalClose} onDeleteTagPress={this.onDeleteTagPress} @@ -176,6 +187,7 @@ Tag.propTypes = { notificationIds: PropTypes.arrayOf(PropTypes.number).isRequired, restrictionIds: PropTypes.arrayOf(PropTypes.number).isRequired, indexerIds: PropTypes.arrayOf(PropTypes.number).isRequired, + downloadClientIds: PropTypes.arrayOf(PropTypes.number).isRequired, authorIds: PropTypes.arrayOf(PropTypes.number).isRequired, onConfirmDeleteTag: PropTypes.func.isRequired }; @@ -186,6 +198,7 @@ Tag.defaultProps = { notificationIds: [], restrictionIds: [], indexerIds: [], + downloadClientIds: [], authorIds: [] }; diff --git a/frontend/src/Settings/Tags/TagsConnector.js b/frontend/src/Settings/Tags/TagsConnector.js index 241ee260a..770dc4720 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, fetchIndexers, fetchNotifications, fetchReleaseProfiles } from 'Store/Actions/settingsActions'; +import { fetchDelayProfiles, fetchDownloadClients, fetchImportLists, fetchIndexers, fetchNotifications, fetchReleaseProfiles } from 'Store/Actions/settingsActions'; import { fetchTagDetails } from 'Store/Actions/tagActions'; import Tags from './Tags'; @@ -30,7 +30,8 @@ const mapDispatchToProps = { dispatchFetchImportLists: fetchImportLists, dispatchFetchNotifications: fetchNotifications, dispatchFetchReleaseProfiles: fetchReleaseProfiles, - dispatchFetchIndexers: fetchIndexers + dispatchFetchIndexers: fetchIndexers, + dispatchFetchDownloadClients: fetchDownloadClients }; class MetadatasConnector extends Component { @@ -45,7 +46,8 @@ class MetadatasConnector extends Component { dispatchFetchImportLists, dispatchFetchNotifications, dispatchFetchReleaseProfiles, - dispatchFetchIndexers + dispatchFetchIndexers, + dispatchFetchDownloadClients } = this.props; dispatchFetchTagDetails(); @@ -54,6 +56,7 @@ class MetadatasConnector extends Component { dispatchFetchNotifications(); dispatchFetchReleaseProfiles(); dispatchFetchIndexers(); + dispatchFetchDownloadClients(); } // @@ -74,7 +77,8 @@ MetadatasConnector.propTypes = { dispatchFetchImportLists: PropTypes.func.isRequired, dispatchFetchNotifications: PropTypes.func.isRequired, dispatchFetchReleaseProfiles: PropTypes.func.isRequired, - dispatchFetchIndexers: PropTypes.func.isRequired + dispatchFetchIndexers: PropTypes.func.isRequired, + dispatchFetchDownloadClients: PropTypes.func.isRequired }; export default connect(createMapStateToProps, mapDispatchToProps)(MetadatasConnector); diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/034_cdh_per_downloadclientFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/034_cdh_per_downloadclientFixture.cs index f78b5d71c..9d6be2d2a 100644 --- a/src/NzbDrone.Core.Test/Datastore/Migration/034_cdh_per_downloadclientFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Migration/034_cdh_per_downloadclientFixture.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using FluentAssertions; using Newtonsoft.Json.Linq; @@ -126,5 +127,6 @@ namespace NzbDrone.Core.Test.Datastore.Migration public string ConfigContract { get; set; } public bool RemoveCompletedDownloads { get; set; } public bool RemoveFailedDownloads { get; set; } + public List Tags { get; set; } } } diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientProviderFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientProviderFixture.cs index 9545801be..8c6ed9fd2 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientProviderFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientProviderFixture.cs @@ -34,7 +34,7 @@ namespace NzbDrone.Core.Test.Download .Returns(_blockedProviders); } - private Mock WithUsenetClient(int priority = 0) + private Mock WithUsenetClient(int priority = 0, HashSet tags = null) { var mock = new Mock(MockBehavior.Default); mock.SetupGet(s => s.Definition) @@ -42,6 +42,7 @@ namespace NzbDrone.Core.Test.Download .CreateNew() .With(v => v.Id = _nextId++) .With(v => v.Priority = priority) + .With(v => v.Tags = tags ?? new HashSet()) .Build()); _downloadClients.Add(mock.Object); @@ -51,7 +52,7 @@ namespace NzbDrone.Core.Test.Download return mock; } - private Mock WithTorrentClient(int priority = 0) + private Mock WithTorrentClient(int priority = 0, HashSet tags = null) { var mock = new Mock(MockBehavior.Default); mock.SetupGet(s => s.Definition) @@ -59,6 +60,7 @@ namespace NzbDrone.Core.Test.Download .CreateNew() .With(v => v.Id = _nextId++) .With(v => v.Priority = priority) + .With(v => v.Tags = tags ?? new HashSet()) .Build()); _downloadClients.Add(mock.Object); @@ -148,6 +150,69 @@ namespace NzbDrone.Core.Test.Download client4.Definition.Id.Should().Be(2); } + [Test] + public void should_roundrobin_over_clients_with_matching_tags() + { + var seriesTags = new HashSet { 1 }; + var clientTags = new HashSet { 1 }; + + WithTorrentClient(); + WithTorrentClient(0, clientTags); + WithTorrentClient(); + WithTorrentClient(0, clientTags); + + var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 0, false, seriesTags); + var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 0, false, seriesTags); + var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 0, false, seriesTags); + var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 0, false, seriesTags); + + client1.Definition.Id.Should().Be(2); + client2.Definition.Id.Should().Be(4); + client3.Definition.Id.Should().Be(2); + client4.Definition.Id.Should().Be(4); + } + + [Test] + public void should_roundrobin_over_non_tagged_when_no_matching_tags() + { + var seriesTags = new HashSet { 2 }; + var clientTags = new HashSet { 1 }; + + WithTorrentClient(); + WithTorrentClient(0, clientTags); + WithTorrentClient(); + WithTorrentClient(0, clientTags); + + var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 0, false, seriesTags); + var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 0, false, seriesTags); + var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 0, false, seriesTags); + var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 0, false, seriesTags); + + client1.Definition.Id.Should().Be(1); + client2.Definition.Id.Should().Be(3); + client3.Definition.Id.Should().Be(1); + client4.Definition.Id.Should().Be(3); + } + + [Test] + public void should_fail_to_choose_when_clients_have_tags_but_no_match() + { + var seriesTags = new HashSet { 2 }; + var clientTags = new HashSet { 1 }; + + WithTorrentClient(0, clientTags); + WithTorrentClient(0, clientTags); + WithTorrentClient(0, clientTags); + WithTorrentClient(0, clientTags); + + var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 0, false, seriesTags); + var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 0, false, seriesTags); + var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 0, false, seriesTags); + var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 0, false, seriesTags); + + Subject.GetDownloadClient(DownloadProtocol.Torrent, 0, false, seriesTags).Should().BeNull(); + } + [Test] public void should_skip_blocked_torrent_client() { @@ -162,7 +227,6 @@ namespace NzbDrone.Core.Test.Download var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent); var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent); var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent); - var client5 = Subject.GetDownloadClient(DownloadProtocol.Torrent); client1.Definition.Id.Should().Be(2); client2.Definition.Id.Should().Be(4); diff --git a/src/NzbDrone.Core.Test/Download/DownloadServiceFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadServiceFixture.cs index b1990ce61..0ea296c9f 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadServiceFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadServiceFixture.cs @@ -31,8 +31,8 @@ namespace NzbDrone.Core.Test.Download .Returns(_downloadClients); Mocker.GetMock() - .Setup(v => v.GetDownloadClient(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((v, i, f) => _downloadClients.FirstOrDefault(d => d.Protocol == v)); + .Setup(v => v.GetDownloadClient(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + .Returns>((v, i, f, t) => _downloadClients.FirstOrDefault(d => d.Protocol == v)); var episodes = Builder.CreateListOfSize(2) .TheFirst(1).With(s => s.Id = 12) diff --git a/src/NzbDrone.Core/Datastore/Migration/035_add_download_client_tags.cs b/src/NzbDrone.Core/Datastore/Migration/035_add_download_client_tags.cs new file mode 100644 index 000000000..77d3d97b3 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/035_add_download_client_tags.cs @@ -0,0 +1,14 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(035)] + public class add_download_client_tags : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("DownloadClients").AddColumn("Tags").AsString().Nullable(); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index d7e42341a..a52c97f73 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -101,8 +101,7 @@ namespace NzbDrone.Core.Datastore Mapper.Entity("DownloadClients").RegisterModel() .Ignore(x => x.ImplementationName) - .Ignore(d => d.Protocol) - .Ignore(d => d.Tags); + .Ignore(d => d.Protocol); Mapper.Entity("History").RegisterModel(); diff --git a/src/NzbDrone.Core/Download/DownloadClientProvider.cs b/src/NzbDrone.Core/Download/DownloadClientProvider.cs index 370b66dea..8e8bcf8be 100644 --- a/src/NzbDrone.Core/Download/DownloadClientProvider.cs +++ b/src/NzbDrone.Core/Download/DownloadClientProvider.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using NLog; using NzbDrone.Common.Cache; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Download.Clients; using NzbDrone.Core.Indexers; @@ -9,7 +10,7 @@ namespace NzbDrone.Core.Download { public interface IProvideDownloadClient { - IDownloadClient GetDownloadClient(DownloadProtocol downloadProtocol, int indexerId = 0, bool filterBlockedClients = false); + IDownloadClient GetDownloadClient(DownloadProtocol downloadProtocol, int indexerId = 0, bool filterBlockedClients = false, HashSet tags = null); IEnumerable GetDownloadClients(bool filterBlockedClients = false); IDownloadClient Get(int id); } @@ -35,11 +36,20 @@ namespace NzbDrone.Core.Download _lastUsedDownloadClient = cacheManager.GetCache(GetType(), "lastDownloadClientId"); } - public IDownloadClient GetDownloadClient(DownloadProtocol downloadProtocol, int indexerId = 0, bool filterBlockedClients = false) + public IDownloadClient GetDownloadClient(DownloadProtocol downloadProtocol, int indexerId = 0, bool filterBlockedClients = false, HashSet tags = null) { var blockedProviders = new HashSet(_downloadClientStatusService.GetBlockedProviders().Select(v => v.ProviderId)); var availableProviders = _downloadClientFactory.GetAvailableProviders().Where(v => v.Protocol == downloadProtocol).ToList(); + if (tags != null) + { + var matchingTagsClients = availableProviders.Where(i => i.Definition.Tags.Intersect(tags).Any()).ToList(); + + availableProviders = matchingTagsClients.Count > 0 ? + matchingTagsClients : + availableProviders.Where(i => i.Definition.Tags.Empty()).ToList(); + } + if (!availableProviders.Any()) { return null; diff --git a/src/NzbDrone.Core/Download/DownloadService.cs b/src/NzbDrone.Core/Download/DownloadService.cs index 7714c26cf..349599b9b 100644 --- a/src/NzbDrone.Core/Download/DownloadService.cs +++ b/src/NzbDrone.Core/Download/DownloadService.cs @@ -56,7 +56,8 @@ namespace NzbDrone.Core.Download var downloadTitle = remoteBook.Release.Title; var filterBlockedClients = remoteBook.Release.PendingReleaseReason == PendingReleaseReason.DownloadClientUnavailable; - var downloadClient = _downloadClientProvider.GetDownloadClient(remoteBook.Release.DownloadProtocol, remoteBook.Release.IndexerId, filterBlockedClients); + var tags = remoteBook.Author?.Tags; + var downloadClient = _downloadClientProvider.GetDownloadClient(remoteBook.Release.DownloadProtocol, remoteBook.Release.IndexerId, filterBlockedClients, tags); if (downloadClient == null) { diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupUnusedTags.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupUnusedTags.cs index 932ddb569..f13321120 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupUnusedTags.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupUnusedTags.cs @@ -19,7 +19,7 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers public void Clean() { using var mapper = _database.OpenConnection(); - var usedTags = new[] { "Authors", "Notifications", "DelayProfiles", "ReleaseProfiles", "ImportLists", "Indexers" } + var usedTags = new[] { "Authors", "Notifications", "DelayProfiles", "ReleaseProfiles", "ImportLists", "Indexers", "DownloadClients" } .SelectMany(v => GetUsedTags(v, mapper)) .Distinct() .ToArray(); diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 80e6f319b..8268ed108 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -255,6 +255,7 @@ "DownloadClientSettings": "Download Client Settings", "DownloadClientStatusCheckAllClientMessage": "All download clients are unavailable due to failures", "DownloadClientStatusCheckSingleClientMessage": "Download clients unavailable due to failures: {0}", + "DownloadClientTagHelpText": "Only use this download client for authors with at least one matching tag. Leave blank to use with all authors.", "DownloadClients": "Download Clients", "DownloadClientsSettingsSummary": "Download clients, download handling and remote path mappings", "DownloadFailedCheckDownloadClientForMoreDetails": "Download failed: check download client for more details", diff --git a/src/NzbDrone.Core/Tags/TagDetails.cs b/src/NzbDrone.Core/Tags/TagDetails.cs index 45eded56b..e6a289451 100644 --- a/src/NzbDrone.Core/Tags/TagDetails.cs +++ b/src/NzbDrone.Core/Tags/TagDetails.cs @@ -14,13 +14,15 @@ namespace NzbDrone.Core.Tags public List ImportListIds { get; set; } public List IndexerIds { get; set; } public List RootFolderIds { get; set; } + public List DownloadClientIds { get; set; } - public bool InUse - { - get - { - return AuthorIds.Any() || NotificationIds.Any() || RestrictionIds.Any() || DelayProfileIds.Any() || ImportListIds.Any() || IndexerIds.Any() || RootFolderIds.Any(); - } - } + public bool InUse => AuthorIds.Any() || + NotificationIds.Any() || + RestrictionIds.Any() || + DelayProfileIds.Any() || + ImportListIds.Any() || + IndexerIds.Any() || + RootFolderIds.Any() || + DownloadClientIds.Any(); } } diff --git a/src/NzbDrone.Core/Tags/TagService.cs b/src/NzbDrone.Core/Tags/TagService.cs index 5fa50dfcc..97f1dc1f9 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.Books; using NzbDrone.Core.Datastore; +using NzbDrone.Core.Download; using NzbDrone.Core.ImportLists; using NzbDrone.Core.Indexers; using NzbDrone.Core.Messaging.Events; @@ -35,6 +36,7 @@ namespace NzbDrone.Core.Tags private readonly IAuthorService _authorService; private readonly IIndexerFactory _indexerService; private readonly IRootFolderService _rootFolderService; + private readonly IDownloadClientFactory _downloadClientFactory; public TagService(ITagRepository repo, IEventAggregator eventAggregator, @@ -44,7 +46,8 @@ namespace NzbDrone.Core.Tags IReleaseProfileService releaseProfileService, IAuthorService authorService, IIndexerFactory indexerService, - IRootFolderService rootFolderService) + IRootFolderService rootFolderService, + IDownloadClientFactory downloadClientFactory) { _repo = repo; _eventAggregator = eventAggregator; @@ -55,6 +58,7 @@ namespace NzbDrone.Core.Tags _authorService = authorService; _indexerService = indexerService; _rootFolderService = rootFolderService; + _downloadClientFactory = downloadClientFactory; } public Tag GetTag(int tagId) @@ -84,6 +88,7 @@ namespace NzbDrone.Core.Tags var author = _authorService.AllForTag(tagId); var indexers = _indexerService.AllForTag(tagId); var rootFolders = _rootFolderService.AllForTag(tagId); + var downloadClients = _downloadClientFactory.AllForTag(tagId); return new TagDetails { @@ -95,7 +100,8 @@ namespace NzbDrone.Core.Tags RestrictionIds = restrictions.Select(c => c.Id).ToList(), AuthorIds = author.Select(c => c.Id).ToList(), IndexerIds = indexers.Select(c => c.Id).ToList(), - RootFolderIds = rootFolders.Select(c => c.Id).ToList() + RootFolderIds = rootFolders.Select(c => c.Id).ToList(), + DownloadClientIds = downloadClients.Select(c => c.Id).ToList() }; } @@ -109,6 +115,7 @@ namespace NzbDrone.Core.Tags var authors = _authorService.GetAllAuthorTags(); var indexers = _indexerService.All(); var rootFolders = _rootFolderService.All(); + var downloadClients = _downloadClientFactory.All(); var details = new List(); @@ -124,7 +131,8 @@ namespace NzbDrone.Core.Tags RestrictionIds = restrictions.Where(c => c.Tags.Contains(tag.Id)).Select(c => c.Id).ToList(), AuthorIds = authors.Where(c => c.Value.Contains(tag.Id)).Select(c => c.Key).ToList(), IndexerIds = indexers.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(), + DownloadClientIds = downloadClients.Where(c => c.Tags.Contains(tag.Id)).Select(c => c.Id).ToList() }); } diff --git a/src/Readarr.Api.V1/Tags/TagDetailsResource.cs b/src/Readarr.Api.V1/Tags/TagDetailsResource.cs index 1d0ec7067..c992ce966 100644 --- a/src/Readarr.Api.V1/Tags/TagDetailsResource.cs +++ b/src/Readarr.Api.V1/Tags/TagDetailsResource.cs @@ -13,6 +13,7 @@ namespace Readarr.Api.V1.Tags public List NotificationIds { get; set; } public List RestrictionIds { get; set; } public List IndexerIds { get; set; } + public List DownloadClientIds { get; set; } public List AuthorIds { get; set; } } @@ -34,6 +35,7 @@ namespace Readarr.Api.V1.Tags NotificationIds = model.NotificationIds, RestrictionIds = model.RestrictionIds, IndexerIds = model.IndexerIds, + DownloadClientIds = model.DownloadClientIds, AuthorIds = model.AuthorIds }; }