diff --git a/frontend/src/Search/SearchIndex.js b/frontend/src/Search/SearchIndex.js
index 21bf3d36b..a4a4c5ef4 100644
--- a/frontend/src/Search/SearchIndex.js
+++ b/frontend/src/Search/SearchIndex.js
@@ -11,13 +11,13 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import { align, icons, sortDirections } from 'Helpers/Props';
-import NoIndexer from 'Indexer/NoIndexer';
import * as keyCodes from 'Utilities/Constants/keyCodes';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
import translate from 'Utilities/String/translate';
import SearchIndexFilterMenu from './Menus/SearchIndexFilterMenu';
import SearchIndexSortMenu from './Menus/SearchIndexSortMenu';
+import NoSearchResults from './NoSearchResults';
import SearchFooter from './SearchFooter.js';
import SearchIndexTableConnector from './Table/SearchIndexTableConnector';
import styles from './SearchIndex.css';
@@ -126,9 +126,8 @@ class SearchIndex extends Component {
this.setState({ jumpToCharacter });
}
- onSearchPress = (query) => {
- console.log('index', query);
- this.props.onSearchPress({ query });
+ onSearchPress = (query, indexerIds) => {
+ this.props.onSearchPress({ query, indexerIds });
}
onKeyUp = (event) => {
@@ -175,6 +174,8 @@ class SearchIndex extends Component {
const isLoaded = !!(!error && isPopulated && items.length && scroller);
const hasNoIndexer = !totalItems;
+ console.log(hasNoIndexer);
+
return (
@@ -246,8 +247,8 @@ class SearchIndex extends Component {
}
{
- !error && isPopulated && !items.length &&
-
+ !error && !isFetching && !items.length &&
+
}
diff --git a/frontend/src/Store/Actions/indexerIndexActions.js b/frontend/src/Store/Actions/indexerIndexActions.js
index 291970dec..0f3a7bd2e 100644
--- a/frontend/src/Store/Actions/indexerIndexActions.js
+++ b/frontend/src/Store/Actions/indexerIndexActions.js
@@ -95,25 +95,7 @@ export const defaultState = {
],
sortPredicates: {
- ...sortPredicates,
-
- studio: function(item) {
- const studio = item.studio;
-
- return studio ? studio.toLowerCase() : '';
- },
-
- collection: function(item) {
- const { collection ={} } = item;
-
- return collection.name;
- },
-
- ratings: function(item) {
- const { ratings = {} } = item;
-
- return ratings.value;
- }
+ ...sortPredicates
},
selectedFilterKey: 'all',
@@ -122,12 +104,6 @@ export const defaultState = {
filterPredicates,
filterBuilderProps: [
- {
- name: 'monitored',
- label: translate('Monitored'),
- type: filterBuilderTypes.EXACT,
- valueType: filterBuilderValueTypes.BOOL
- },
{
name: 'title',
label: 'Indexer Name',
diff --git a/frontend/src/Store/Actions/movieActions.js b/frontend/src/Store/Actions/movieActions.js
index 4aa7d55fc..5be6399da 100644
--- a/frontend/src/Store/Actions/movieActions.js
+++ b/frontend/src/Store/Actions/movieActions.js
@@ -1,11 +1,10 @@
import _ from 'lodash';
import { createAction } from 'redux-actions';
-import { filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props';
+import { sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
// import { batchActions } from 'redux-batched-actions';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import dateFilterPredicate from 'Utilities/Date/dateFilterPredicate';
-import padNumber from 'Utilities/Number/padNumber';
import translate from 'Utilities/String/translate';
import { updateItem } from './baseActions';
import createFetchHandler from './Creators/createFetchHandler';
@@ -24,123 +23,12 @@ export const filters = [
key: 'all',
label: translate('All'),
filters: []
- },
- {
- key: 'monitored',
- label: translate('MonitoredOnly'),
- filters: [
- {
- key: 'monitored',
- value: true,
- type: filterTypes.EQUAL
- }
- ]
- },
- {
- key: 'unmonitored',
- label: translate('Unmonitored'),
- filters: [
- {
- key: 'monitored',
- value: false,
- type: filterTypes.EQUAL
- }
- ]
- },
- {
- key: 'missing',
- label: translate('Missing'),
- filters: [
- {
- key: 'monitored',
- value: true,
- type: filterTypes.EQUAL
- },
- {
- key: 'hasFile',
- value: false,
- type: filterTypes.EQUAL
- }
- ]
- },
- {
- key: 'wanted',
- label: translate('Wanted'),
- filters: [
- {
- key: 'monitored',
- value: true,
- type: filterTypes.EQUAL
- },
- {
- key: 'hasFile',
- value: false,
- type: filterTypes.EQUAL
- },
- {
- key: 'isAvailable',
- value: true,
- type: filterTypes.EQUAL
- }
- ]
- },
- {
- key: 'cutoffunmet',
- label: translate('CutoffUnmet'),
- filters: [
- {
- key: 'monitored',
- value: true,
- type: filterTypes.EQUAL
- },
- {
- key: 'hasFile',
- value: true,
- type: filterTypes.EQUAL
- },
- {
- key: 'qualityCutoffNotMet',
- value: true,
- type: filterTypes.EQUAL
- }
- ]
}
];
export const filterPredicates = {
added: function(item, filterValue, type) {
return dateFilterPredicate(item.added, filterValue, type);
- },
-
- collection: function(item, filterValue, type) {
- const predicate = filterTypePredicates[type];
- const { collection } = item;
-
- return predicate(collection ? collection.name : '', filterValue);
- },
-
- inCinemas: function(item, filterValue, type) {
- return dateFilterPredicate(item.inCinemas, filterValue, type);
- },
-
- physicalRelease: function(item, filterValue, type) {
- return dateFilterPredicate(item.physicalRelease, filterValue, type);
- },
-
- digitalRelease: function(item, filterValue, type) {
- return dateFilterPredicate(item.digitalRelease, filterValue, type);
- },
-
- ratings: function(item, filterValue, type) {
- const predicate = filterTypePredicates[type];
-
- return predicate(item.ratings.value * 10, filterValue);
- },
-
- qualityCutoffNotMet: function(item) {
- const { movieFile = {} } = item;
-
- return movieFile.qualityCutoffNotMet;
}
};
@@ -165,33 +53,6 @@ export const sortPredicates = {
}
return result;
- },
-
- movieStatus: function(item) {
- let result = 0;
- let qualityName = '';
-
- const hasMovieFile = !!item.movieFile;
-
- if (item.isAvailable) {
- result++;
- }
-
- if (item.monitored) {
- result += 2;
- }
-
- if (hasMovieFile) {
- // TODO: Consider Quality Weight for Sorting within status of hasMovie
- if (item.movieFile.qualityCutoffNotMet) {
- result += 4;
- } else {
- result += 8;
- }
- qualityName = item.movieFile.quality.quality.name;
- }
-
- return padNumber(result.toString(), 2) + qualityName;
}
};
@@ -205,7 +66,7 @@ export const defaultState = {
isSaving: false,
saveError: null,
items: [],
- sortKey: 'sortTitle',
+ sortKey: 'name',
sortDirection: sortDirections.ASCENDING,
pendingChanges: {}
};
diff --git a/frontend/src/Store/Actions/movieTitlesActions.js b/frontend/src/Store/Actions/movieTitlesActions.js
deleted file mode 100644
index 8a53ae87b..000000000
--- a/frontend/src/Store/Actions/movieTitlesActions.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import { batchActions } from 'redux-batched-actions';
-import { createThunk, handleThunks } from 'Store/thunks';
-import createAjaxRequest from 'Utilities/createAjaxRequest';
-import { set, update } from './baseActions';
-import createHandleActions from './Creators/createHandleActions';
-
-//
-// Variables
-
-export const section = 'movieTitles';
-
-//
-// State
-
-export const defaultState = {
- isFetching: false,
- isPopulated: false,
- error: null,
- items: []
-};
-
-//
-// Actions Types
-
-export const FETCH_MOVIE_TITLES = 'movieTitles/fetchMovieTitles';
-
-//
-// Action Creators
-
-export const fetchMovieTitles = createThunk(FETCH_MOVIE_TITLES);
-
-//
-// Action Handlers
-
-export const actionHandlers = handleThunks({
-
- [FETCH_MOVIE_TITLES]: function(getState, payload, dispatch) {
- dispatch(set({ section, isFetching: true }));
-
- const promise = createAjaxRequest({
- url: '/alttitle',
- data: payload
- }).request;
-
- promise.done((data) => {
- dispatch(batchActions([
- update({ section, data }),
-
- set({
- section,
- isFetching: false,
- isPopulated: true,
- error: null
- })
- ]));
- });
-
- promise.fail((xhr) => {
- dispatch(set({
- section,
- isFetching: false,
- isPopulated: false,
- error: xhr
- }));
- });
- }
-});
-
-//
-// Reducers
-
-export const reducers = createHandleActions({
-
-}, defaultState, section);
diff --git a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabCapabilitiesProviderFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabCapabilitiesProviderFixture.cs
index ff9731ac4..bd9d316bb 100644
--- a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabCapabilitiesProviderFixture.cs
+++ b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabCapabilitiesProviderFixture.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Xml;
using FluentAssertions;
using Moq;
@@ -6,7 +6,6 @@ using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Newznab;
using NzbDrone.Core.Test.Framework;
-using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
{
@@ -53,8 +52,8 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
var caps = Subject.GetCapabilities(_settings);
- caps.DefaultPageSize.Should().Be(25);
- caps.MaxPageSize.Should().Be(60);
+ caps.LimitsDefault.Value.Should().Be(25);
+ caps.LimitsMax.Value.Should().Be(60);
}
[Test]
@@ -64,8 +63,8 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
var caps = Subject.GetCapabilities(_settings);
- caps.DefaultPageSize.Should().Be(100);
- caps.MaxPageSize.Should().Be(100);
+ caps.LimitsDefault.Value.Should().Be(100);
+ caps.LimitsMax.Value.Should().Be(100);
}
[Test]
diff --git a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabFixture.cs
index e31bfc35b..71dafecfc 100644
--- a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabFixture.cs
+++ b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabFixture.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Linq;
using FluentAssertions;
using Moq;
@@ -13,7 +13,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
[TestFixture]
public class NewznabFixture : CoreTest
{
- private NewznabCapabilities _caps;
+ private IndexerCapabilities _caps;
[SetUp]
public void Setup()
@@ -29,7 +29,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
}
};
- _caps = new NewznabCapabilities();
+ _caps = new IndexerCapabilities();
Mocker.GetMock()
.Setup(v => v.GetCapabilities(It.IsAny()))
.Returns(_caps);
@@ -64,8 +64,8 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
[Test]
public void should_use_pagesize_reported_by_caps()
{
- _caps.MaxPageSize = 30;
- _caps.DefaultPageSize = 25;
+ _caps.LimitsMax = 30;
+ _caps.LimitsDefault = 25;
Subject.PageSize.Should().Be(25);
}
diff --git a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabRequestGeneratorFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabRequestGeneratorFixture.cs
index 14c99d4f2..89b86cef4 100644
--- a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabRequestGeneratorFixture.cs
+++ b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabRequestGeneratorFixture.cs
@@ -3,6 +3,7 @@ using System.Linq;
using FluentAssertions;
using Moq;
using NUnit.Framework;
+using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Newznab;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework;
@@ -12,7 +13,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
public class NewznabRequestGeneratorFixture : CoreTest
{
private MovieSearchCriteria _movieSearchCriteria;
- private NewznabCapabilities _capabilities;
+ private IndexerCapabilities _capabilities;
[SetUp]
public void SetUp()
@@ -29,7 +30,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
SceneTitles = new List { "Star Wars" }
};
- _capabilities = new NewznabCapabilities();
+ _capabilities = new IndexerCapabilities();
Mocker.GetMock()
.Setup(v => v.GetCapabilities(It.IsAny()))
@@ -91,7 +92,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
[Test]
public void should_not_search_by_imdbid_if_not_supported()
{
- _capabilities.SupportedMovieSearchParameters = new[] { "q" };
+ _capabilities.MovieSearchParams = new List { MovieSearchParam.Q };
var results = Subject.GetSearchRequests(_movieSearchCriteria);
@@ -106,7 +107,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
[Test]
public void should_search_by_imdbid_if_supported()
{
- _capabilities.SupportedMovieSearchParameters = new[] { "q", "imdbid" };
+ _capabilities.MovieSearchParams = new List { MovieSearchParam.Q, MovieSearchParam.ImdbId };
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.GetTier(0).Should().HaveCount(1);
@@ -119,7 +120,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
[Test]
public void should_search_by_tmdbid_if_supported()
{
- _capabilities.SupportedMovieSearchParameters = new[] { "q", "tmdbid" };
+ _capabilities.MovieSearchParams = new List { MovieSearchParam.Q, MovieSearchParam.TmdbId };
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.GetTier(0).Should().HaveCount(1);
@@ -132,7 +133,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
[Test]
public void should_prefer_search_by_tmdbid_if_rid_supported()
{
- _capabilities.SupportedMovieSearchParameters = new[] { "q", "tmdbid", "imdbid" };
+ _capabilities.MovieSearchParams = new List { MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId };
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.GetTier(0).Should().HaveCount(1);
@@ -146,8 +147,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
[Test]
public void should_use_aggregrated_id_search_if_supported()
{
- _capabilities.SupportedMovieSearchParameters = new[] { "q", "tmdbid", "imdbid" };
- _capabilities.SupportsAggregateIdSearch = true;
+ _capabilities.MovieSearchParams = new List { MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId };
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.GetTier(0).Should().HaveCount(1);
@@ -161,8 +161,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
[Test]
public void should_not_use_aggregrated_id_search_if_no_ids_supported()
{
- _capabilities.SupportedMovieSearchParameters = new[] { "q" };
- _capabilities.SupportsAggregateIdSearch = true; // Turns true if indexer supplies supportedParams.
+ _capabilities.MovieSearchParams = new List { MovieSearchParam.Q };
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.Tiers.Should().Be(1);
@@ -176,8 +175,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
[Test]
public void should_not_use_aggregrated_id_search_if_no_ids_are_known()
{
- _capabilities.SupportedMovieSearchParameters = new[] { "q", "imdbid" };
- _capabilities.SupportsAggregateIdSearch = true; // Turns true if indexer supplies supportedParams.
+ _capabilities.MovieSearchParams = new List { MovieSearchParam.Q, MovieSearchParam.ImdbId };
_movieSearchCriteria.ImdbId = null;
@@ -191,8 +189,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
[Test]
public void should_fallback_to_q()
{
- _capabilities.SupportedMovieSearchParameters = new[] { "q", "tmdbid", "imdbid" };
- _capabilities.SupportsAggregateIdSearch = true;
+ _capabilities.MovieSearchParams = new List { MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId };
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.Tiers.Should().Be(2);
diff --git a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabSettingFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabSettingFixture.cs
index 51e2227d4..33eee6e54 100644
--- a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabSettingFixture.cs
+++ b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabSettingFixture.cs
@@ -1,4 +1,4 @@
-using FluentAssertions;
+using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Indexers.Newznab;
using NzbDrone.Core.Test.Framework;
diff --git a/src/NzbDrone.Core.Test/IndexerTests/TorznabTests/TorznabFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/TorznabTests/TorznabFixture.cs
index d65c92660..f4dd499d1 100644
--- a/src/NzbDrone.Core.Test/IndexerTests/TorznabTests/TorznabFixture.cs
+++ b/src/NzbDrone.Core.Test/IndexerTests/TorznabTests/TorznabFixture.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Linq;
using FluentAssertions;
using Moq;
@@ -15,7 +15,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
[TestFixture]
public class TorznabFixture : CoreTest
{
- private NewznabCapabilities _caps;
+ private IndexerCapabilities _caps;
[SetUp]
public void Setup()
@@ -30,7 +30,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
}
};
- _caps = new NewznabCapabilities();
+ _caps = new IndexerCapabilities();
Mocker.GetMock()
.Setup(v => v.GetCapabilities(It.IsAny()))
.Returns(_caps);
@@ -129,8 +129,8 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
[Test]
public void should_use_pagesize_reported_by_caps()
{
- _caps.MaxPageSize = 30;
- _caps.DefaultPageSize = 25;
+ _caps.LimitsMax = 30;
+ _caps.LimitsDefault = 25;
Subject.PageSize.Should().Be(25);
}
diff --git a/src/NzbDrone.Core.Test/Prowlarr.Core.Test.csproj b/src/NzbDrone.Core.Test/Prowlarr.Core.Test.csproj
index f18d88820..929199a29 100644
--- a/src/NzbDrone.Core.Test/Prowlarr.Core.Test.csproj
+++ b/src/NzbDrone.Core.Test/Prowlarr.Core.Test.csproj
@@ -10,6 +10,7 @@
+
diff --git a/src/NzbDrone.Core.Test/ThingiProviderTests/ProviderBaseFixture.cs b/src/NzbDrone.Core.Test/ThingiProviderTests/ProviderBaseFixture.cs
index 1faf6d750..600206a24 100644
--- a/src/NzbDrone.Core.Test/ThingiProviderTests/ProviderBaseFixture.cs
+++ b/src/NzbDrone.Core.Test/ThingiProviderTests/ProviderBaseFixture.cs
@@ -1,4 +1,4 @@
-using FizzWare.NBuilder;
+using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Indexers;
diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs
index 94f96d974..ef766fbca 100644
--- a/src/NzbDrone.Core/Datastore/TableMapping.cs
+++ b/src/NzbDrone.Core/Datastore/TableMapping.cs
@@ -7,7 +7,6 @@ using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.CustomFilters;
using NzbDrone.Core.Datastore.Converters;
-using NzbDrone.Core.History;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Instrumentation;
using NzbDrone.Core.Jobs;
@@ -82,7 +81,6 @@ namespace NzbDrone.Core.Datastore
SqlMapper.AddTypeHandler(new DapperLanguageIntConverter());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>(new LanguageIntConverter()));
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>());
- SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter(new LanguageIntConverter()));
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>());
SqlMapper.AddTypeHandler(new OsPathConverter());
diff --git a/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs b/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs
index 2a8ea0f5a..0836089b7 100644
--- a/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs
+++ b/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs
@@ -13,11 +13,11 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
private static readonly Regex BeginningThe = new Regex(@"^the\s", RegexOptions.IgnoreCase | RegexOptions.Compiled);
public List SceneTitles { get; set; }
- public virtual bool MonitoredEpisodesOnly { get; set; }
public virtual bool UserInvokedSearch { get; set; }
public virtual bool InteractiveSearch { get; set; }
public string ImdbId { get; set; }
public int TmdbId { get; set; }
+ public List IndexerIds { get; set; }
public List QueryTitles => SceneTitles.Select(GetQueryTitle).ToList();
diff --git a/src/NzbDrone.Core/IndexerSearch/MoviesSearchCommand.cs b/src/NzbDrone.Core/IndexerSearch/MoviesSearchCommand.cs
deleted file mode 100644
index 7c3b00e5e..000000000
--- a/src/NzbDrone.Core/IndexerSearch/MoviesSearchCommand.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using NzbDrone.Core.Messaging.Commands;
-
-namespace NzbDrone.Core.IndexerSearch
-{
- public class MoviesSearchCommand : Command
- {
- public string SearchTerm { get; set; }
-
- public override bool SendUpdatesToClient => true;
- }
-}
diff --git a/src/NzbDrone.Core/IndexerSearch/MoviesSearchService.cs b/src/NzbDrone.Core/IndexerSearch/MoviesSearchService.cs
deleted file mode 100644
index 7d9b52b47..000000000
--- a/src/NzbDrone.Core/IndexerSearch/MoviesSearchService.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using NLog;
-using NzbDrone.Common.Instrumentation.Extensions;
-using NzbDrone.Core.Messaging.Commands;
-
-namespace NzbDrone.Core.IndexerSearch
-{
- public class MovieSearchService : IExecute
- {
- private readonly ISearchForNzb _nzbSearchService;
- private readonly Logger _logger;
-
- public MovieSearchService(ISearchForNzb nzbSearchService,
- Logger logger)
- {
- _nzbSearchService = nzbSearchService;
- _logger = logger;
- }
-
- public void Execute(MoviesSearchCommand message)
- {
- var decisions = _nzbSearchService.MovieSearch(message.SearchTerm, false, false);
-
- _logger.ProgressInfo("Movie search completed. {0} reports downloaded.", decisions.Count);
- }
- }
-}
diff --git a/src/NzbDrone.Core/IndexerSearch/NewznabRequest.cs b/src/NzbDrone.Core/IndexerSearch/NewznabRequest.cs
new file mode 100644
index 000000000..81f900959
--- /dev/null
+++ b/src/NzbDrone.Core/IndexerSearch/NewznabRequest.cs
@@ -0,0 +1,28 @@
+namespace NzbDrone.Core.IndexerSearch
+{
+ public class NewznabRequest
+ {
+ public int id { get; set; }
+ public string t { get; set; }
+ public string q { get; set; }
+ public string cat { get; set; }
+ public string imdbid { get; set; }
+ public string tmdbid { get; set; }
+ public string extended { get; set; }
+ public string limit { get; set; }
+ public string offset { get; set; }
+ public string rid { get; set; }
+ public string tvdbid { get; set; }
+ public string season { get; set; }
+ public string ep { get; set; }
+ public string album { get; set; }
+ public string artist { get; set; }
+ public string label { get; set; }
+ public string track { get; set; }
+ public string year { get; set; }
+ public string genre { get; set; }
+ public string author { get; set; }
+ public string title { get; set; }
+ public string configured { get; set; }
+ }
+}
diff --git a/src/NzbDrone.Core/IndexerSearch/NewznabResults.cs b/src/NzbDrone.Core/IndexerSearch/NewznabResults.cs
new file mode 100644
index 000000000..58493d90d
--- /dev/null
+++ b/src/NzbDrone.Core/IndexerSearch/NewznabResults.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Xml.Linq;
+using NzbDrone.Core.Parser.Model;
+
+namespace NzbDrone.Core.IndexerSearch
+{
+ public class NewznabResults
+ {
+ private static readonly XNamespace _AtomNs = "http://www.w3.org/2005/Atom";
+ private static readonly XNamespace _TorznabNs = "http://torznab.com/schemas/2015/feed";
+
+ // filters control characters but allows only properly-formed surrogate sequences
+ // https://stackoverflow.com/a/961504
+ private static readonly Regex _InvalidXmlChars = new Regex(
+ @"(? Releases;
+
+ private static string RemoveInvalidXMLChars(string text)
+ {
+ if (text == null)
+ {
+ return null;
+ }
+
+ return _InvalidXmlChars.Replace(text, "");
+ }
+
+ private static string XmlDateFormat(DateTime dt)
+ {
+ Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
+
+ //Sat, 14 Mar 2015 17:10:42 -0400
+ return $"{dt:ddd, dd MMM yyyy HH:mm:ss} " + $"{dt:zzz}".Replace(":", "");
+ }
+
+ private static XElement GetTorznabElement(string name, object value)
+ {
+ if (value == null)
+ {
+ return null;
+ }
+
+ return new XElement(_TorznabNs + "attr", new XAttribute("name", name), new XAttribute("value", value));
+ }
+
+ public string ToXml()
+ {
+ // IMPORTANT: We can't use Uri.ToString(), because it generates URLs without URL encode (links with unicode
+ // characters are broken). We must use Uri.AbsoluteUri instead that handles encoding correctly
+ var xdoc = new XDocument(
+ new XDeclaration("1.0", "UTF-8", null),
+ new XElement("rss",
+ new XAttribute("version", "1.0"),
+ new XAttribute(XNamespace.Xmlns + "atom", _AtomNs.NamespaceName),
+ new XAttribute(XNamespace.Xmlns + "torznab", _TorznabNs.NamespaceName),
+ new XElement("channel",
+ new XElement(_AtomNs + "link",
+ new XAttribute("rel", "self"),
+ new XAttribute("type", "application/rss+xml")),
+ new XElement("title", "Prowlarr"),
+ from r in Releases
+ let t = (r as TorrentInfo) ?? new TorrentInfo()
+ select new XElement("item",
+ new XElement("title", RemoveInvalidXMLChars(r.Title)),
+ new XElement("guid", r.Guid), // GUID and (Link or Magnet) are mandatory
+ new XElement("prowlarrindexer", new XAttribute("id", r.IndexerId), r.Indexer),
+ r.PublishDate == DateTime.MinValue ? new XElement("pubDate", XmlDateFormat(DateTime.Now)) : new XElement("pubDate", XmlDateFormat(r.PublishDate)),
+ new XElement("size", r.Size),
+ new XElement(
+ "enclosure",
+ new XAttribute("length", r.Size),
+ new XAttribute("type", "application/x-bittorrent")),
+ GetTorznabElement("rageid", r.TvRageId),
+ GetTorznabElement("thetvdb", r.TvdbId),
+ GetTorznabElement("imdb", r.ImdbId.ToString("D7")),
+ GetTorznabElement("tmdb", r.TmdbId),
+ GetTorznabElement("seeders", t.Seeders),
+ GetTorznabElement("peers", t.Peers),
+ GetTorznabElement("infohash", RemoveInvalidXMLChars(r.Guid))))));
+
+ return xdoc.Declaration + Environment.NewLine + xdoc;
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs
index b723c7322..df0e64102 100644
--- a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs
+++ b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Common.Extensions;
@@ -13,7 +14,8 @@ namespace NzbDrone.Core.IndexerSearch
{
public interface ISearchForNzb
{
- List MovieSearch(string movieId, bool userInvokedSearch, bool interactiveSearch);
+ List Search(string query, List indexerIds, bool userInvokedSearch, bool interactiveSearch);
+ NewznabResults Search(NewznabRequest request, List indexerIds, bool userInvokedSearch, bool interactiveSearch);
}
public class NzbSearchService : ISearchForNzb
@@ -28,14 +30,21 @@ namespace NzbDrone.Core.IndexerSearch
_logger = logger;
}
- public List MovieSearch(string movie, bool userInvokedSearch, bool interactiveSearch)
+ public List Search(string query, List indexerIds, bool userInvokedSearch, bool interactiveSearch)
{
- var searchSpec = Get(movie, userInvokedSearch, interactiveSearch);
+ var searchSpec = Get(query, indexerIds, userInvokedSearch, interactiveSearch);
return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
}
- private TSpec Get(string movie, bool userInvokedSearch, bool interactiveSearch)
+ public NewznabResults Search(NewznabRequest request, List indexerIds, bool userInvokedSearch, bool interactiveSearch)
+ {
+ var searchSpec = Get(request.q, indexerIds, userInvokedSearch, interactiveSearch);
+
+ return new NewznabResults { Releases = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec) };
+ }
+
+ private TSpec Get(string query, List indexerIds, bool userInvokedSearch, bool interactiveSearch)
where TSpec : SearchCriteriaBase, new()
{
var spec = new TSpec()
@@ -44,7 +53,8 @@ namespace NzbDrone.Core.IndexerSearch
InteractiveSearch = interactiveSearch
};
- spec.SceneTitles = new List { movie };
+ spec.SceneTitles = new List { query };
+ spec.IndexerIds = indexerIds;
return spec;
}
@@ -55,6 +65,11 @@ namespace NzbDrone.Core.IndexerSearch
_indexerFactory.InteractiveSearchEnabled() :
_indexerFactory.AutomaticSearchEnabled();
+ if (criteriaBase.IndexerIds != null && criteriaBase.IndexerIds.Count > 0)
+ {
+ indexers = indexers.Where(i => criteriaBase.IndexerIds.Contains(i.Definition.Id)).ToList();
+ }
+
var reports = new List();
_logger.ProgressInfo("Searching {0} indexers for {1}", indexers.Count, criteriaBase.QueryTitles.Join(", "));
diff --git a/src/NzbDrone.Core/Indexers/Definitions/Newznab/Newznab.cs b/src/NzbDrone.Core/Indexers/Definitions/Newznab/Newznab.cs
index 3cf28551a..055f6d2fc 100644
--- a/src/NzbDrone.Core/Indexers/Definitions/Newznab/Newznab.cs
+++ b/src/NzbDrone.Core/Indexers/Definitions/Newznab/Newznab.cs
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.Indexers.Newznab
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
- public override int PageSize => _capabilitiesProvider.GetCapabilities(Settings).DefaultPageSize;
+ public override int PageSize => _capabilitiesProvider.GetCapabilities(Settings).LimitsDefault.Value;
public override IIndexerRequestGenerator GetRequestGenerator()
{
@@ -36,6 +36,14 @@ namespace NzbDrone.Core.Indexers.Newznab
return new NewznabRssParser(Settings);
}
+ public override IndexerCapabilities GetCapabilities()
+ {
+ // TODO: This uses indexer capabilities when called so we don't have to keep up with all of them
+ // however, this is not pulled on a all pull from UI, doing so will kill the UI load if an indexer is down
+ // should we just purge and manage
+ return _capabilitiesProvider.GetCapabilities(Settings);
+ }
+
public override IEnumerable DefaultDefinitions
{
get
@@ -75,7 +83,8 @@ namespace NzbDrone.Core.Indexers.Newznab
Protocol = DownloadProtocol.Usenet,
Privacy = IndexerPrivacy.Private,
SupportsRss = SupportsRss,
- SupportsSearch = SupportsSearch
+ SupportsSearch = SupportsSearch,
+ Capabilities = Capabilities
};
}
@@ -107,15 +116,15 @@ namespace NzbDrone.Core.Indexers.Newznab
failures.AddIfNotNull(TestCapabilities());
}
- protected static List CategoryIds(List categories)
+ protected static List CategoryIds(List categories)
{
var l = categories.Select(c => c.Id).ToList();
foreach (var category in categories)
{
- if (category.Subcategories != null)
+ if (category.SubCategories != null)
{
- l.AddRange(CategoryIds(category.Subcategories));
+ l.AddRange(CategoryIds(category.SubCategories));
}
}
@@ -139,14 +148,14 @@ namespace NzbDrone.Core.Indexers.Newznab
}
}
- if (capabilities.SupportedSearchParameters != null && capabilities.SupportedSearchParameters.Contains("q"))
+ if (capabilities.SearchParams != null && capabilities.SearchParams.Contains(SearchParam.Q))
{
return null;
}
- if (capabilities.SupportedMovieSearchParameters != null &&
- new[] { "q", "imdbid" }.Any(v => capabilities.SupportedMovieSearchParameters.Contains(v)) &&
- new[] { "imdbtitle", "imdbyear" }.All(v => capabilities.SupportedMovieSearchParameters.Contains(v)))
+ if (capabilities.MovieSearchParams != null &&
+ new[] { MovieSearchParam.Q, MovieSearchParam.ImdbId }.Any(v => capabilities.MovieSearchParams.Contains(v)) &&
+ new[] { MovieSearchParam.ImdbTitle, MovieSearchParam.ImdbYear }.All(v => capabilities.MovieSearchParams.Contains(v)))
{
return null;
}
diff --git a/src/NzbDrone.Core/Indexers/Definitions/Newznab/NewznabCapabilities.cs b/src/NzbDrone.Core/Indexers/Definitions/Newznab/NewznabCapabilities.cs
deleted file mode 100644
index 7961b0b2e..000000000
--- a/src/NzbDrone.Core/Indexers/Definitions/Newznab/NewznabCapabilities.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System.Collections.Generic;
-
-namespace NzbDrone.Core.Indexers.Newznab
-{
- public class NewznabCapabilities
- {
- public int DefaultPageSize { get; set; }
- public int MaxPageSize { get; set; }
- public string[] SupportedSearchParameters { get; set; }
- public string[] SupportedMovieSearchParameters { get; set; }
- public bool SupportsAggregateIdSearch { get; set; }
- public List Categories { get; set; }
-
- public NewznabCapabilities()
- {
- DefaultPageSize = 100;
- MaxPageSize = 100;
- SupportedSearchParameters = new[] { "q" };
- SupportedMovieSearchParameters = new[] { "q", "imdbid", "imdbtitle", "imdbyear" };
- SupportsAggregateIdSearch = false;
- Categories = new List();
- }
- }
-
- public class NewznabCategory
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public string Description { get; set; }
-
- public List Subcategories { get; set; }
- }
-}
diff --git a/src/NzbDrone.Core/Indexers/Definitions/Newznab/NewznabCapabilitiesProvider.cs b/src/NzbDrone.Core/Indexers/Definitions/Newznab/NewznabCapabilitiesProvider.cs
index deae3ea5d..6042ecb65 100644
--- a/src/NzbDrone.Core/Indexers/Definitions/Newznab/NewznabCapabilitiesProvider.cs
+++ b/src/NzbDrone.Core/Indexers/Definitions/Newznab/NewznabCapabilitiesProvider.cs
@@ -12,23 +12,23 @@ namespace NzbDrone.Core.Indexers.Newznab
{
public interface INewznabCapabilitiesProvider
{
- NewznabCapabilities GetCapabilities(NewznabSettings settings);
+ IndexerCapabilities GetCapabilities(NewznabSettings settings);
}
public class NewznabCapabilitiesProvider : INewznabCapabilitiesProvider
{
- private readonly ICached _capabilitiesCache;
+ private readonly ICached _capabilitiesCache;
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
public NewznabCapabilitiesProvider(ICacheManager cacheManager, IHttpClient httpClient, Logger logger)
{
- _capabilitiesCache = cacheManager.GetCache(GetType());
+ _capabilitiesCache = cacheManager.GetCache(GetType());
_httpClient = httpClient;
_logger = logger;
}
- public NewznabCapabilities GetCapabilities(NewznabSettings indexerSettings)
+ public IndexerCapabilities GetCapabilities(NewznabSettings indexerSettings)
{
var key = indexerSettings.ToJson();
var capabilities = _capabilitiesCache.Get(key, () => FetchCapabilities(indexerSettings), TimeSpan.FromDays(7));
@@ -36,9 +36,9 @@ namespace NzbDrone.Core.Indexers.Newznab
return capabilities;
}
- private NewznabCapabilities FetchCapabilities(NewznabSettings indexerSettings)
+ private IndexerCapabilities FetchCapabilities(NewznabSettings indexerSettings)
{
- var capabilities = new NewznabCapabilities();
+ var capabilities = new IndexerCapabilities();
var url = string.Format("{0}{1}?t=caps", indexerSettings.BaseUrl.TrimEnd('/'), indexerSettings.ApiPath.TrimEnd('/'));
@@ -81,9 +81,9 @@ namespace NzbDrone.Core.Indexers.Newznab
return capabilities;
}
- private NewznabCapabilities ParseCapabilities(HttpResponse response)
+ private IndexerCapabilities ParseCapabilities(HttpResponse response)
{
- var capabilities = new NewznabCapabilities();
+ var capabilities = new IndexerCapabilities();
var xDoc = XDocument.Parse(response.Content);
@@ -104,8 +104,8 @@ namespace NzbDrone.Core.Indexers.Newznab
var xmlLimits = xmlRoot.Element("limits");
if (xmlLimits != null)
{
- capabilities.DefaultPageSize = int.Parse(xmlLimits.Attribute("default").Value);
- capabilities.MaxPageSize = int.Parse(xmlLimits.Attribute("max").Value);
+ capabilities.LimitsDefault = int.Parse(xmlLimits.Attribute("default").Value);
+ capabilities.LimitsMax = int.Parse(xmlLimits.Attribute("max").Value);
}
var xmlSearching = xmlRoot.Element("searching");
@@ -114,22 +114,81 @@ namespace NzbDrone.Core.Indexers.Newznab
var xmlBasicSearch = xmlSearching.Element("search");
if (xmlBasicSearch == null || xmlBasicSearch.Attribute("available").Value != "yes")
{
- capabilities.SupportedSearchParameters = null;
+ capabilities.SearchParams = new List();
}
else if (xmlBasicSearch.Attribute("supportedParams") != null)
{
- capabilities.SupportedSearchParameters = xmlBasicSearch.Attribute("supportedParams").Value.Split(',');
+ foreach (var param in xmlBasicSearch.Attribute("supportedParams").Value.Split(','))
+ {
+ if (Enum.TryParse(param, true, out SearchParam searchParam))
+ {
+ capabilities.SearchParams.AddIfNotNull(searchParam);
+ }
+ }
}
var xmlMovieSearch = xmlSearching.Element("movie-search");
if (xmlMovieSearch == null || xmlMovieSearch.Attribute("available").Value != "yes")
{
- capabilities.SupportedMovieSearchParameters = null;
+ capabilities.MovieSearchParams = new List();
}
else if (xmlMovieSearch.Attribute("supportedParams") != null)
{
- capabilities.SupportedMovieSearchParameters = xmlMovieSearch.Attribute("supportedParams").Value.Split(',');
- capabilities.SupportsAggregateIdSearch = true;
+ foreach (var param in xmlMovieSearch.Attribute("supportedParams").Value.Split(','))
+ {
+ if (Enum.TryParse(param, true, out MovieSearchParam searchParam))
+ {
+ capabilities.MovieSearchParams.AddIfNotNull(searchParam);
+ }
+ }
+ }
+
+ var xmlTvSearch = xmlSearching.Element("tv-search");
+ if (xmlTvSearch == null || xmlTvSearch.Attribute("available").Value != "yes")
+ {
+ capabilities.TvSearchParams = new List();
+ }
+ else if (xmlTvSearch.Attribute("supportedParams") != null)
+ {
+ foreach (var param in xmlTvSearch.Attribute("supportedParams").Value.Split(','))
+ {
+ if (Enum.TryParse(param, true, out TvSearchParam searchParam))
+ {
+ capabilities.TvSearchParams.AddIfNotNull(searchParam);
+ }
+ }
+ }
+
+ var xmlAudioSearch = xmlSearching.Element("audio-search");
+ if (xmlAudioSearch == null || xmlAudioSearch.Attribute("available").Value != "yes")
+ {
+ capabilities.MusicSearchParams = new List();
+ }
+ else if (xmlAudioSearch.Attribute("supportedParams") != null)
+ {
+ foreach (var param in xmlAudioSearch.Attribute("supportedParams").Value.Split(','))
+ {
+ if (Enum.TryParse(param, true, out MusicSearchParam searchParam))
+ {
+ capabilities.MusicSearchParams.AddIfNotNull(searchParam);
+ }
+ }
+ }
+
+ var xmlBookSearch = xmlSearching.Element("book-search");
+ if (xmlBookSearch == null || xmlBookSearch.Attribute("available").Value != "yes")
+ {
+ capabilities.BookSearchParams = new List();
+ }
+ else if (xmlBookSearch.Attribute("supportedParams") != null)
+ {
+ foreach (var param in xmlBookSearch.Attribute("supportedParams").Value.Split(','))
+ {
+ if (Enum.TryParse(param, true, out BookSearchParam searchParam))
+ {
+ capabilities.BookSearchParams.AddIfNotNull(searchParam);
+ }
+ }
}
}
@@ -138,17 +197,16 @@ namespace NzbDrone.Core.Indexers.Newznab
{
foreach (var xmlCategory in xmlCategories.Elements("category"))
{
- var cat = new NewznabCategory
+ var cat = new IndexerCategory
{
Id = int.Parse(xmlCategory.Attribute("id").Value),
Name = xmlCategory.Attribute("name").Value,
- Description = xmlCategory.Attribute("description") != null ? xmlCategory.Attribute("description").Value : string.Empty,
- Subcategories = new List()
+ Description = xmlCategory.Attribute("description") != null ? xmlCategory.Attribute("description").Value : string.Empty
};
foreach (var xmlSubcat in xmlCategory.Elements("subcat"))
{
- cat.Subcategories.Add(new NewznabCategory
+ cat.SubCategories.Add(new IndexerCategory
{
Id = int.Parse(xmlSubcat.Attribute("id").Value),
Name = xmlSubcat.Attribute("name").Value,
diff --git a/src/NzbDrone.Core/Indexers/Definitions/Newznab/NewznabRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Definitions/Newznab/NewznabRequestGenerator.cs
index a4aef34ec..b8c26fca0 100644
--- a/src/NzbDrone.Core/Indexers/Definitions/Newznab/NewznabRequestGenerator.cs
+++ b/src/NzbDrone.Core/Indexers/Definitions/Newznab/NewznabRequestGenerator.cs
@@ -28,8 +28,8 @@ namespace NzbDrone.Core.Indexers.Newznab
{
var capabilities = _capabilitiesProvider.GetCapabilities(Settings);
- return capabilities.SupportedSearchParameters != null &&
- capabilities.SupportedSearchParameters.Contains("q");
+ return capabilities.SearchParams != null &&
+ capabilities.SearchParams.Contains(SearchParam.Q);
}
}
@@ -39,8 +39,8 @@ namespace NzbDrone.Core.Indexers.Newznab
{
var capabilities = _capabilitiesProvider.GetCapabilities(Settings);
- return capabilities.SupportedMovieSearchParameters != null &&
- capabilities.SupportedMovieSearchParameters.Contains("imdbid");
+ return capabilities.MovieSearchParams != null &&
+ capabilities.MovieSearchParams.Contains(MovieSearchParam.ImdbId);
}
}
@@ -50,8 +50,8 @@ namespace NzbDrone.Core.Indexers.Newznab
{
var capabilities = _capabilitiesProvider.GetCapabilities(Settings);
- return capabilities.SupportedMovieSearchParameters != null &&
- capabilities.SupportedMovieSearchParameters.Contains("tmdbid");
+ return capabilities.MovieSearchParams != null &&
+ capabilities.MovieSearchParams.Contains(MovieSearchParam.TmdbId);
}
}
@@ -61,7 +61,8 @@ namespace NzbDrone.Core.Indexers.Newznab
{
var capabilities = _capabilitiesProvider.GetCapabilities(Settings);
- return capabilities.SupportsAggregateIdSearch;
+ // TODO: Fix this, return capabilities.SupportsAggregateIdSearch;
+ return true;
}
}
@@ -72,11 +73,11 @@ namespace NzbDrone.Core.Indexers.Newznab
var capabilities = _capabilitiesProvider.GetCapabilities(Settings);
// Some indexers might forget to enable movie search, but normal search still works fine. Thus we force a normal search.
- if (capabilities.SupportedMovieSearchParameters != null)
+ if (capabilities.MovieSearchParams != null)
{
pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "movie", ""));
}
- else if (capabilities.SupportedSearchParameters != null)
+ else if (capabilities.SearchParams != null)
{
pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "search", ""));
}
diff --git a/src/NzbDrone.Core/Indexers/Definitions/Newznab/NewznabRssParser.cs b/src/NzbDrone.Core/Indexers/Definitions/Newznab/NewznabRssParser.cs
index 460974fb5..e6f6e0f3b 100644
--- a/src/NzbDrone.Core/Indexers/Definitions/Newznab/NewznabRssParser.cs
+++ b/src/NzbDrone.Core/Indexers/Definitions/Newznab/NewznabRssParser.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.Linq;
using System.Xml.Linq;
using NzbDrone.Common.Extensions;
@@ -148,19 +147,6 @@ namespace NzbDrone.Core.Indexers.Newznab
return 0;
}
- protected virtual string GetImdbTitle(XElement item)
- {
- var imdbTitle = TryGetNewznabAttribute(item, "imdbtitle");
- if (!imdbTitle.IsNullOrWhiteSpace())
- {
- return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(
- Parser.Parser.ReplaceGermanUmlauts(
- Parser.Parser.NormalizeTitle(imdbTitle).Replace(" ", ".")));
- }
-
- return string.Empty;
- }
-
protected virtual int GetImdbYear(XElement item)
{
var imdbYearString = TryGetNewznabAttribute(item, "imdbyear");
diff --git a/src/NzbDrone.Core/Indexers/Definitions/Torznab/Torznab.cs b/src/NzbDrone.Core/Indexers/Definitions/Torznab/Torznab.cs
index 0f265daed..764c08a67 100644
--- a/src/NzbDrone.Core/Indexers/Definitions/Torznab/Torznab.cs
+++ b/src/NzbDrone.Core/Indexers/Definitions/Torznab/Torznab.cs
@@ -20,7 +20,8 @@ namespace NzbDrone.Core.Indexers.Torznab
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
- public override int PageSize => _capabilitiesProvider.GetCapabilities(Settings).DefaultPageSize;
+
+ public override int PageSize => _capabilitiesProvider.GetCapabilities(Settings).LimitsDefault.Value;
public override IIndexerRequestGenerator GetRequestGenerator()
{
@@ -63,7 +64,8 @@ namespace NzbDrone.Core.Indexers.Torznab
Settings = settings,
Protocol = DownloadProtocol.Usenet,
SupportsRss = SupportsRss,
- SupportsSearch = SupportsSearch
+ SupportsSearch = SupportsSearch,
+ Capabilities = new IndexerCapabilities()
};
}
@@ -95,15 +97,15 @@ namespace NzbDrone.Core.Indexers.Torznab
failures.AddIfNotNull(TestCapabilities());
}
- protected static List CategoryIds(List categories)
+ protected static List CategoryIds(List categories)
{
var l = categories.Select(c => c.Id).ToList();
foreach (var category in categories)
{
- if (category.Subcategories != null)
+ if (category.SubCategories != null)
{
- l.AddRange(CategoryIds(category.Subcategories));
+ l.AddRange(CategoryIds(category.SubCategories));
}
}
@@ -127,14 +129,14 @@ namespace NzbDrone.Core.Indexers.Torznab
}
}
- if (capabilities.SupportedSearchParameters != null && capabilities.SupportedSearchParameters.Contains("q"))
+ if (capabilities.SearchParams != null && capabilities.SearchParams.Contains(SearchParam.Q))
{
return null;
}
- if (capabilities.SupportedMovieSearchParameters != null &&
- new[] { "q", "imdbid" }.Any(v => capabilities.SupportedMovieSearchParameters.Contains(v)) &&
- new[] { "imdbtitle", "imdbyear" }.All(v => capabilities.SupportedMovieSearchParameters.Contains(v)))
+ if (capabilities.MovieSearchParams != null &&
+ new[] { MovieSearchParam.Q, MovieSearchParam.ImdbId }.Any(v => capabilities.MovieSearchParams.Contains(v)) &&
+ new[] { MovieSearchParam.ImdbTitle, MovieSearchParam.ImdbYear }.All(v => capabilities.MovieSearchParams.Contains(v)))
{
return null;
}
diff --git a/src/NzbDrone.Core/Indexers/Definitions/Torznab/TorznabSettings.cs b/src/NzbDrone.Core/Indexers/Definitions/Torznab/TorznabSettings.cs
index 75ec82e92..7e2a979b7 100644
--- a/src/NzbDrone.Core/Indexers/Definitions/Torznab/TorznabSettings.cs
+++ b/src/NzbDrone.Core/Indexers/Definitions/Torznab/TorznabSettings.cs
@@ -1,11 +1,8 @@
-using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using FluentValidation;
using NzbDrone.Common.Extensions;
-using NzbDrone.Core.Annotations;
using NzbDrone.Core.Indexers.Newznab;
-using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Torznab
@@ -47,22 +44,14 @@ namespace NzbDrone.Core.Indexers.Torznab
}
}
- public class TorznabSettings : NewznabSettings, ITorrentIndexerSettings
+ public class TorznabSettings : NewznabSettings
{
private static readonly TorznabSettingsValidator Validator = new TorznabSettingsValidator();
public TorznabSettings()
{
- MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
- RequiredFlags = new List();
}
- [FieldDefinition(8, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
- public int MinimumSeeders { get; set; }
-
- [FieldDefinition(9, Type = FieldType.TagSelect, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://github.com/Prowlarr/Prowlarr/wiki/Indexer-Flags#1-required-flags", Advanced = true)]
- public IEnumerable RequiredFlags { get; set; }
-
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
diff --git a/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs b/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs
index 2a6a10932..7dc2ea842 100644
--- a/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs
+++ b/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs
@@ -86,6 +86,11 @@ namespace NzbDrone.Core.Indexers
return requests;
}
+ public override IndexerCapabilities GetCapabilities()
+ {
+ return Capabilities;
+ }
+
protected virtual IList FetchReleases(Func pageableRequestChainSelector, bool isRecent = false)
{
var releases = new List();
diff --git a/src/NzbDrone.Core/Indexers/IIndexer.cs b/src/NzbDrone.Core/Indexers/IIndexer.cs
index a05e42362..d13506844 100644
--- a/src/NzbDrone.Core/Indexers/IIndexer.cs
+++ b/src/NzbDrone.Core/Indexers/IIndexer.cs
@@ -16,5 +16,7 @@ namespace NzbDrone.Core.Indexers
IList FetchRecent();
IList Fetch(MovieSearchCriteria searchCriteria);
+
+ IndexerCapabilities GetCapabilities();
}
}
diff --git a/src/NzbDrone.Core/Indexers/IndexerBase.cs b/src/NzbDrone.Core/Indexers/IndexerBase.cs
index 160ec444a..3f0b984e1 100644
--- a/src/NzbDrone.Core/Indexers/IndexerBase.cs
+++ b/src/NzbDrone.Core/Indexers/IndexerBase.cs
@@ -68,6 +68,8 @@ namespace NzbDrone.Core.Indexers
public abstract IList FetchRecent();
public abstract IList Fetch(MovieSearchCriteria searchCriteria);
+ public abstract IndexerCapabilities GetCapabilities();
+
protected virtual IList CleanupReleases(IEnumerable releases)
{
var result = releases.DistinctBy(v => v.Guid).ToList();
diff --git a/src/NzbDrone.Core/Indexers/IndexerCapabilities.cs b/src/NzbDrone.Core/Indexers/IndexerCapabilities.cs
index f7f5144db..e7c8546f1 100644
--- a/src/NzbDrone.Core/Indexers/IndexerCapabilities.cs
+++ b/src/NzbDrone.Core/Indexers/IndexerCapabilities.cs
@@ -13,13 +13,16 @@ namespace NzbDrone.Core.Indexers
ImdbId,
TvdbId,
RId,
+ TvMazeId
}
public enum MovieSearchParam
{
Q,
ImdbId,
- TmdbId
+ TmdbId,
+ ImdbTitle,
+ ImdbYear
}
public enum MusicSearchParam
@@ -31,12 +34,23 @@ namespace NzbDrone.Core.Indexers
Year
}
+ public enum SearchParam
+ {
+ Q
+ }
+
+ public enum BookSearchParam
+ {
+ Q
+ }
+
public class IndexerCapabilities
{
public int? LimitsMax { get; set; }
public int? LimitsDefault { get; set; }
- public bool SearchAvailable { get; set; }
+ public List SearchParams;
+ public bool SearchAvailable => SearchParams.Count > 0;
public List TvSearchParams;
public bool TvSearchAvailable => TvSearchParams.Count > 0;
@@ -45,6 +59,7 @@ namespace NzbDrone.Core.Indexers
public bool TvSearchImdbAvailable => TvSearchParams.Contains(TvSearchParam.ImdbId);
public bool TvSearchTvdbAvailable => TvSearchParams.Contains(TvSearchParam.TvdbId);
public bool TvSearchTvRageAvailable => TvSearchParams.Contains(TvSearchParam.RId);
+ public bool TvSearchTvMazeAvailable => TvSearchParams.Contains(TvSearchParam.TvMazeId);
public List MovieSearchParams;
public bool MovieSearchAvailable => MovieSearchParams.Count > 0;
@@ -58,17 +73,18 @@ namespace NzbDrone.Core.Indexers
public bool MusicSearchLabelAvailable => MusicSearchParams.Contains(MusicSearchParam.Label);
public bool MusicSearchYearAvailable => MusicSearchParams.Contains(MusicSearchParam.Year);
- public bool BookSearchAvailable { get; set; }
+ public List BookSearchParams;
+ public bool BookSearchAvailable => BookSearchParams.Count > 0;
public List Categories { get; private set; }
public IndexerCapabilities()
{
- SearchAvailable = true;
+ SearchParams = new List();
TvSearchParams = new List();
MovieSearchParams = new List();
MusicSearchParams = new List();
- BookSearchAvailable = false;
+ BookSearchParams = new List();
Categories = new List();
}
@@ -181,6 +197,11 @@ namespace NzbDrone.Core.Indexers
parameters.Add("rid");
}
+ if (TvSearchTvMazeAvailable)
+ {
+ parameters.Add("tvmazeid");
+ }
+
return string.Join(",", parameters);
}
@@ -244,7 +265,7 @@ namespace NzbDrone.Core.Indexers
{
var subCategories = Categories.SelectMany(c => c.SubCategories);
var allCategories = Categories.Concat(subCategories);
- var supportsCategory = allCategories.Any(i => categories.Any(c => c == i.ID));
+ var supportsCategory = allCategories.Any(i => categories.Any(c => c == i.Id));
return supportsCategory;
}
@@ -280,13 +301,13 @@ namespace NzbDrone.Core.Indexers
new XAttribute("available", BookSearchAvailable ? "yes" : "no"),
new XAttribute("supportedParams", SupportedBookSearchParams))),
new XElement("categories",
- from c in Categories.OrderBy(x => x.ID < 100000 ? "z" + x.ID.ToString() : x.Name)
+ from c in Categories.OrderBy(x => x.Id < 100000 ? "z" + x.Id.ToString() : x.Name)
select new XElement("category",
- new XAttribute("id", c.ID),
+ new XAttribute("id", c.Id),
new XAttribute("name", c.Name),
from sc in c.SubCategories
select new XElement("subcat",
- new XAttribute("id", sc.ID),
+ new XAttribute("id", sc.Id),
new XAttribute("name", sc.Name))))));
return xdoc;
}
@@ -296,12 +317,12 @@ namespace NzbDrone.Core.Indexers
public static IndexerCapabilities Concat(IndexerCapabilities left, IndexerCapabilities right)
{
- left.SearchAvailable = left.SearchAvailable || right.SearchAvailable;
+ left.SearchParams = left.SearchParams.Union(right.SearchParams).ToList();
left.TvSearchParams = left.TvSearchParams.Union(right.TvSearchParams).ToList();
left.MovieSearchParams = left.MovieSearchParams.Union(right.MovieSearchParams).ToList();
left.MusicSearchParams = left.MusicSearchParams.Union(right.MusicSearchParams).ToList();
- left.BookSearchAvailable = left.BookSearchAvailable || right.BookSearchAvailable;
- left.Categories.AddRange(right.Categories.Where(x => x.ID < 100000).Except(left.Categories)); // exclude indexer specific categories (>= 100000)
+ left.BookSearchParams = left.BookSearchParams.Union(right.BookSearchParams).ToList();
+ left.Categories.AddRange(right.Categories.Where(x => x.Id < 100000).Except(left.Categories)); // exclude indexer specific categories (>= 100000)
return left;
}
}
diff --git a/src/NzbDrone.Core/Indexers/IndexerCategory.cs b/src/NzbDrone.Core/Indexers/IndexerCategory.cs
index 2d4489ab0..3ac8d29eb 100644
--- a/src/NzbDrone.Core/Indexers/IndexerCategory.cs
+++ b/src/NzbDrone.Core/Indexers/IndexerCategory.cs
@@ -5,8 +5,9 @@ namespace NzbDrone.Core.Indexers
{
public class IndexerCategory
{
- public int ID { get; set; }
+ public int Id { get; set; }
public string Name { get; set; }
+ public string Description { get; set; }
public List SubCategories { get; private set; }
@@ -14,7 +15,7 @@ namespace NzbDrone.Core.Indexers
public IndexerCategory(int id, string name)
{
- ID = id;
+ Id = id;
Name = name;
SubCategories = new List();
}
@@ -25,14 +26,14 @@ namespace NzbDrone.Core.Indexers
public JToken ToJson() =>
new JObject
{
- ["ID"] = ID,
+ ["ID"] = Id,
["Name"] = Name
};
- public override bool Equals(object obj) => (obj as IndexerCategory)?.ID == ID;
+ public override bool Equals(object obj) => (obj as IndexerCategory)?.Id == Id;
// Get Hash code should be calculated off read only properties.
// ID is not readonly
- public override int GetHashCode() => ID;
+ public override int GetHashCode() => Id;
}
}
diff --git a/src/NzbDrone.Core/Parser/Model/ParsedMovieInfo.cs b/src/NzbDrone.Core/Parser/Model/ParsedMovieInfo.cs
deleted file mode 100644
index 7a8c2d9ed..000000000
--- a/src/NzbDrone.Core/Parser/Model/ParsedMovieInfo.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System.Collections.Generic;
-using Newtonsoft.Json;
-using NzbDrone.Core.Languages;
-
-namespace NzbDrone.Core.Parser.Model
-{
- public class ParsedMovieInfo
- {
- public string MovieTitle { get; set; }
- public string OriginalTitle { get; set; }
- public string ReleaseTitle { get; set; }
- public string SimpleReleaseTitle { get; set; }
- public List Languages { get; set; } = new List();
- public string ReleaseGroup { get; set; }
- public string ReleaseHash { get; set; }
- public string Edition { get; set; }
- public int Year { get; set; }
- public string ImdbId { get; set; }
- [JsonIgnore]
- public Dictionary ExtraInfo { get; set; } = new Dictionary();
-
- public override string ToString()
- {
- return string.Format("{0} - {1}", MovieTitle, Year);
- }
- }
-}
diff --git a/src/NzbDrone.Core/Parser/Model/ReleaseInfo.cs b/src/NzbDrone.Core/Parser/Model/ReleaseInfo.cs
index c9f6069f5..3f72c93e5 100644
--- a/src/NzbDrone.Core/Parser/Model/ReleaseInfo.cs
+++ b/src/NzbDrone.Core/Parser/Model/ReleaseInfo.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Text;
using NzbDrone.Core.Indexers;
@@ -19,6 +19,7 @@ namespace NzbDrone.Core.Parser.Model
public int TvdbId { get; set; }
public int TvRageId { get; set; }
public int ImdbId { get; set; }
+ public int TmdbId { get; set; }
public DateTime PublishDate { get; set; }
public string Origin { get; set; }
diff --git a/src/NzbDrone.Core/Parser/Model/SeriesTitleInfo.cs b/src/NzbDrone.Core/Parser/Model/SeriesTitleInfo.cs
deleted file mode 100644
index e9befbf39..000000000
--- a/src/NzbDrone.Core/Parser/Model/SeriesTitleInfo.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace NzbDrone.Core.Parser.Model
-{
- public class SeriesTitleInfo
- {
- public string Title { get; set; }
- public string TitleWithoutYear { get; set; }
- public int Year { get; set; }
- }
-}
diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs
deleted file mode 100644
index 8a3361a2d..000000000
--- a/src/NzbDrone.Core/Parser/Parser.cs
+++ /dev/null
@@ -1,584 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text.RegularExpressions;
-using NLog;
-using NzbDrone.Common.Extensions;
-using NzbDrone.Common.Instrumentation;
-using NzbDrone.Core.Parser.Model;
-#if !LIBRARY
-#endif
-
-namespace NzbDrone.Core.Parser
-{
- public static class Parser
- {
- private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(Parser));
-
- private static readonly Regex ReportYearRegex = new Regex(@"^.*(?(19|20)\d{2}).*$", RegexOptions.Compiled);
-
- private static readonly Regex EditionRegex = new Regex(@"\(?\b(?(((Recut.|Extended.|Ultimate.)?(Director.?s|Collector.?s|Theatrical|Ultimate|Final(?=(.(Cut|Edition|Version)))|Extended|Rogue|Special|Despecialized|\d{2,3}(th)?.Anniversary)(.(Cut|Edition|Version))?(.(Extended|Uncensored|Remastered|Unrated|Uncut|IMAX|Fan.?Edit))?|((Uncensored|Remastered|Unrated|Uncut|IMAX|Fan.?Edit|Edition|Restored|((2|3|4)in1))))))\b\)?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
-
- private static readonly Regex ReportEditionRegex = new Regex(@"^.+?" + EditionRegex, RegexOptions.Compiled | RegexOptions.IgnoreCase);
-
- private static readonly Regex[] ReportMovieTitleRegex = new[]
- {
- //Some german or french tracker formats (missing year, ...) (Only applies to german and French/TrueFrench releases) - see ParserFixture for examples and tests
- new Regex(@"^(?(?![(\[]).+?)((\W|_))(" + EditionRegex + @".{1,3})?(?:(?(19|20)\d{2}(?!p|i|\d+|\]|\W\d+))?(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled),
-
- //Special, Despecialized, etc. Edition Movies, e.g: Mission.Impossible.3.Special.Edition.2011
- new Regex(@"^(?(?![(\[]).+?)?(?:(?:[-_\W](?(1(8|9)|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)",
- RegexOptions.IgnoreCase | RegexOptions.Compiled),
-
- //Special, Despecialized, etc. Edition Movies, e.g: Mission.Impossible.3.2011.Special.Edition //TODO: Seems to slow down parsing heavily!
- /*new Regex(@"^(?(?![(\[]).+?)?(?:(?:[-_\W](?(19|20)\d{2}(?!p|i|(19|20)\d{2}|\]|\W(19|20)\d{2})))+(\W+|_|$)(?!\\)\(?(?(((Extended.|Ultimate.)?(Director.?s|Collector.?s|Theatrical|Ultimate|Final(?=(.(Cut|Edition|Version)))|Extended|Rogue|Special|Despecialized|\d{2,3}(th)?.Anniversary)(.(Cut|Edition|Version))?(.(Extended|Uncensored|Remastered|Unrated|Uncut|IMAX|Fan.?Edit))?|((Uncensored|Remastered|Unrated|Uncut|IMAX|Fan.?Edit|Edition|Restored|((2|3|4)in1))))))\)?",
- RegexOptions.IgnoreCase | RegexOptions.Compiled),*/
-
- //Normal movie format, e.g: Mission.Impossible.3.2011
- new Regex(@"^(?(?![(\[]).+?)?(?:(?:[-_\W](?(1(8|9)|20)\d{2}(?!p|i|(1(8|9)|20)\d{2}|\]|\W(1(8|9)|20)\d{2})))+(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled),
-
- //PassThePopcorn Torrent names: Star.Wars[PassThePopcorn]
- new Regex(@"^(?.+?)?(?:(?:[-_\W](?(\[\w *\])))+(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled),
-
- //That did not work? Maybe some tool uses [] for years. Who would do that?
- new Regex(@"^(?(?![(\[]).+?)?(?:(?:[-_\W](?(1(8|9)|20)\d{2}(?!p|i|\d+|\W\d+)))+(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled),
-
- //As a last resort for movies that have ( or [ in their title.
- new Regex(@"^(?.+?)?(?:(?:[-_\W](?(1(8|9)|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled),
- };
-
- private static readonly Regex[] ReportMovieTitleFolderRegex = new[]
- {
- //When year comes first.
- new Regex(@"^(?:(?:[-_\W](?(19|20)\d{2}(?!p|i|\d+|\W\d+)))+(\W+|_|$)(?.+?)?$")
- };
-
- private static readonly Regex[] RejectHashedReleasesRegex = new Regex[]
- {
- // Generic match for md5 and mixed-case hashes.
- new Regex(@"^[0-9a-zA-Z]{32}", RegexOptions.Compiled),
-
- // Generic match for shorter lower-case hashes.
- new Regex(@"^[a-z0-9]{24}$", RegexOptions.Compiled),
-
- // Format seen on some NZBGeek releases
- // Be very strict with these coz they are very close to the valid 101 ep numbering.
- new Regex(@"^[A-Z]{11}\d{3}$", RegexOptions.Compiled),
- new Regex(@"^[a-z]{12}\d{3}$", RegexOptions.Compiled),
-
- //Backup filename (Unknown origins)
- new Regex(@"^Backup_\d{5,}S\d{2}-\d{2}$", RegexOptions.Compiled),
-
- //123 - Started appearing December 2014
- new Regex(@"^123$", RegexOptions.Compiled),
-
- //abc - Started appearing January 2015
- new Regex(@"^abc$", RegexOptions.Compiled | RegexOptions.IgnoreCase),
-
- //abc - Started appearing 2020
- new Regex(@"^abc[-_. ]xyz", RegexOptions.Compiled | RegexOptions.IgnoreCase),
-
- //b00bs - Started appearing January 2015
- new Regex(@"^b00bs$", RegexOptions.Compiled | RegexOptions.IgnoreCase)
- };
-
- //Regex to detect whether the title was reversed.
- private static readonly Regex ReversedTitleRegex = new Regex(@"(?:^|[-._ ])(p027|p0801)[-._ ]", RegexOptions.Compiled);
-
- private static readonly Regex NormalizeRegex = new Regex(@"((?:\b|_)(?tt\d{7,8})", RegexOptions.IgnoreCase | RegexOptions.Compiled);
-
- private static readonly RegexReplace SimpleTitleRegex = new RegexReplace(@"\s*(?:480[ip]|576[ip]|720[ip]|1080[ip]|2160[ip]|[xh][\W_]?26[45]|DD\W?5\W1|[<>?*:|]|848x480|1280x720|1920x1080|(8|10)b(it)?)",
- string.Empty,
- RegexOptions.IgnoreCase | RegexOptions.Compiled);
-
- private static readonly Regex SimpleReleaseTitleRegex = new Regex(@"\s*(?:[<>?*:|])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
-
- private static readonly RegexReplace WebsitePrefixRegex = new RegexReplace(@"^\[\s*[-a-z]+(\.[a-z]+)+\s*\][- ]*|^www\.[a-z]+\.(?:com|net|org)[ -]*",
- string.Empty,
- RegexOptions.IgnoreCase | RegexOptions.Compiled);
-
- private static readonly RegexReplace WebsitePostfixRegex = new RegexReplace(@"\[\s*[-a-z]+(\.[a-z0-9]+)+\s*\]$",
- string.Empty,
- RegexOptions.IgnoreCase | RegexOptions.Compiled);
-
- private static readonly RegexReplace CleanReleaseGroupRegex = new RegexReplace(@"(-(RP|1|NZBGeek|Obfuscated|Obfuscation|Scrambled|sample|Pre|postbot|xpost|Rakuv[a-z0-9]*|WhiteRev|BUYMORE|AsRequested|AlternativeToRequested|GEROV|Z0iDS3N|Chamele0n|4P|4Planet|AlteZachen))+$",
- string.Empty,
- RegexOptions.IgnoreCase | RegexOptions.Compiled);
-
- private static readonly RegexReplace CleanTorrentSuffixRegex = new RegexReplace(@"\[(?:ettv|rartv|rarbg|cttv)\]$",
- string.Empty,
- RegexOptions.IgnoreCase | RegexOptions.Compiled);
-
- private static readonly Regex CleanQualityBracketsRegex = new Regex(@"\[[a-z0-9 ._-]+\]$",
- RegexOptions.IgnoreCase | RegexOptions.Compiled);
-
- private static readonly Regex ReleaseGroupRegex = new Regex(@"-(?[a-z0-9]+(?!.+?(?:480p|720p|1080p|2160p)))(?[a-z0-9]+)\]$",
- RegexOptions.IgnoreCase | RegexOptions.Compiled);
-
- private static readonly Regex AnimeReleaseGroupRegex = new Regex(@"^(?:\[(?(?!\s).+?(?.+?)(?:\W|_)?(?\d{4})",
- RegexOptions.IgnoreCase | RegexOptions.Compiled);
-
- private static readonly Regex WordDelimiterRegex = new Regex(@"(\s|\.|,|_|-|=|'|\|)+", RegexOptions.Compiled);
- private static readonly Regex SpecialCharRegex = new Regex(@"(\&|\:|\\|\/)+", RegexOptions.Compiled);
- private static readonly Regex PunctuationRegex = new Regex(@"[^\w\s]", RegexOptions.Compiled);
- private static readonly Regex CommonWordRegex = new Regex(@"\b(a|an|the|and|or|of)\b\s?", RegexOptions.IgnoreCase | RegexOptions.Compiled);
- private static readonly Regex SpecialEpisodeWordRegex = new Regex(@"\b(part|special|edition|christmas)\b\s?", RegexOptions.IgnoreCase | RegexOptions.Compiled);
- private static readonly Regex DuplicateSpacesRegex = new Regex(@"\s{2,}", RegexOptions.Compiled);
-
- private static readonly Regex RequestInfoRegex = new Regex(@"^(?:\[.+?\])+", RegexOptions.Compiled);
-
- private static readonly string[] Numbers = new[] { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };
- private static Dictionary _umlautMappings = new Dictionary
- {
- { "ö", "oe" },
- { "ä", "ae" },
- { "ü", "ue" },
- };
-
- public static ParsedMovieInfo ParseMoviePath(string path)
- {
- var fileInfo = new FileInfo(path);
-
- var result = ParseMovieTitle(fileInfo.Name, true);
-
- if (result == null)
- {
- Logger.Debug("Attempting to parse movie info using directory and file names. {0}", fileInfo.Directory.Name);
- result = ParseMovieTitle(fileInfo.Directory.Name + " " + fileInfo.Name);
- }
-
- if (result == null)
- {
- Logger.Debug("Attempting to parse movie info using directory name. {0}", fileInfo.Directory.Name);
- result = ParseMovieTitle(fileInfo.Directory.Name + fileInfo.Extension);
- }
-
- return result;
- }
-
- public static ParsedMovieInfo ParseMovieTitle(string title, bool isDir = false)
- {
- var originalTitle = title;
- try
- {
- if (!ValidateBeforeParsing(title))
- {
- return null;
- }
-
- Logger.Debug("Parsing string '{0}'", title);
-
- if (ReversedTitleRegex.IsMatch(title))
- {
- var titleWithoutExtension = RemoveFileExtension(title).ToCharArray();
- Array.Reverse(titleWithoutExtension);
-
- title = new string(titleWithoutExtension) + title.Substring(titleWithoutExtension.Length);
-
- Logger.Debug("Reversed name detected. Converted to '{0}'", title);
- }
-
- var releaseTitle = RemoveFileExtension(title);
-
- //Trim dashes from end
- releaseTitle = releaseTitle.Trim('-', '_');
-
- releaseTitle = releaseTitle.Replace("【", "[").Replace("】", "]");
-
- var simpleTitle = SimpleTitleRegex.Replace(releaseTitle);
-
- // TODO: Quick fix stripping [url] - prefixes.
- simpleTitle = WebsitePrefixRegex.Replace(simpleTitle);
- simpleTitle = WebsitePostfixRegex.Replace(simpleTitle);
-
- simpleTitle = CleanTorrentSuffixRegex.Replace(simpleTitle);
-
- var allRegexes = ReportMovieTitleRegex.ToList();
-
- if (isDir)
- {
- allRegexes.AddRange(ReportMovieTitleFolderRegex);
- }
-
- foreach (var regex in allRegexes)
- {
- var match = regex.Matches(simpleTitle);
-
- if (match.Count != 0)
- {
- Logger.Trace(regex);
- try
- {
- var result = ParseMovieMatchCollection(match);
-
- if (result != null)
- {
- //TODO: Add tests for this!
- var simpleReleaseTitle = SimpleReleaseTitleRegex.Replace(releaseTitle, string.Empty);
-
- var simpleTitleReplaceString = match[0].Groups["title"].Success ? match[0].Groups["title"].Value : result.MovieTitle;
-
- if (simpleTitleReplaceString.IsNotNullOrWhiteSpace())
- {
- simpleReleaseTitle = simpleReleaseTitle.Replace(simpleTitleReplaceString, simpleTitleReplaceString.Contains(".") ? "A.Movie" : "A Movie");
- }
-
- result.ReleaseGroup = ParseReleaseGroup(simpleReleaseTitle);
-
- var subGroup = GetSubGroup(match);
- if (!subGroup.IsNullOrWhiteSpace())
- {
- result.ReleaseGroup = subGroup;
- }
-
- Logger.Debug("Release Group parsed: {0}", result.ReleaseGroup);
-
- result.Languages = LanguageParser.ParseLanguages(result.ReleaseGroup.IsNotNullOrWhiteSpace() ? simpleReleaseTitle.Replace(result.ReleaseGroup, "RlsGrp") : simpleReleaseTitle);
- Logger.Debug("Languages parsed: {0}", string.Join(", ", result.Languages));
-
- if (result.Edition.IsNullOrWhiteSpace())
- {
- result.Edition = ParseEdition(simpleReleaseTitle);
- Logger.Debug("Edition parsed: {0}", result.Edition);
- }
-
- result.ReleaseHash = GetReleaseHash(match);
- if (!result.ReleaseHash.IsNullOrWhiteSpace())
- {
- Logger.Debug("Release Hash parsed: {0}", result.ReleaseHash);
- }
-
- result.OriginalTitle = originalTitle;
- result.ReleaseTitle = releaseTitle;
- result.SimpleReleaseTitle = simpleReleaseTitle;
-
- result.ImdbId = ParseImdbId(simpleReleaseTitle);
-
- return result;
- }
- }
- catch (InvalidDateException ex)
- {
- Logger.Debug(ex, ex.Message);
- break;
- }
- }
- }
- }
- catch (Exception e)
- {
- if (!title.ToLower().Contains("password") && !title.ToLower().Contains("yenc"))
- {
- Logger.Error(e, "An error has occurred while trying to parse {0}", title);
- }
- }
-
- Logger.Debug("Unable to parse {0}", title);
- return null;
- }
-
- public static string ParseImdbId(string title)
- {
- var match = ReportImdbId.Match(title);
- if (match.Success)
- {
- if (match.Groups["imdbid"].Value != null)
- {
- if (match.Groups["imdbid"].Length == 9 || match.Groups["imdbid"].Length == 10)
- {
- return match.Groups["imdbid"].Value;
- }
- }
- }
-
- return "";
- }
-
- public static string ParseEdition(string languageTitle)
- {
- var editionMatch = ReportEditionRegex.Match(languageTitle);
-
- if (editionMatch.Success && editionMatch.Groups["edition"].Value != null &&
- editionMatch.Groups["edition"].Value.IsNotNullOrWhiteSpace())
- {
- return editionMatch.Groups["edition"].Value.Replace(".", " ");
- }
-
- return "";
- }
-
- public static string ReplaceGermanUmlauts(string s)
- {
- var t = s;
- t = t.Replace("ä", "ae");
- t = t.Replace("ö", "oe");
- t = t.Replace("ü", "ue");
- t = t.Replace("Ä", "Ae");
- t = t.Replace("Ö", "Oe");
- t = t.Replace("Ü", "Ue");
- t = t.Replace("ß", "ss");
- return t;
- }
-
- public static string NormalizeImdbId(string imdbId)
- {
- if (imdbId.Length > 2)
- {
- imdbId = imdbId.Replace("tt", "").PadLeft(7, '0');
- return $"tt{imdbId}";
- }
-
- return null;
- }
-
- public static string ToUrlSlug(string value)
- {
- //First to lower case
- value = value.ToLowerInvariant();
-
- //Remove all accents
- value = value.RemoveAccent();
-
- //Replace spaces
- value = Regex.Replace(value, @"\s", "-", RegexOptions.Compiled);
-
- //Remove invalid chars
- value = Regex.Replace(value, @"[^a-z0-9\s-_]", "", RegexOptions.Compiled);
-
- //Trim dashes from end
- value = value.Trim('-', '_');
-
- //Replace double occurences of - or _
- value = Regex.Replace(value, @"([-_]){2,}", "$1", RegexOptions.Compiled);
-
- return value;
- }
-
- public static string CleanMovieTitle(this string title)
- {
- long number = 0;
-
- //If Title only contains numbers return it as is.
- if (long.TryParse(title, out number))
- {
- return title;
- }
-
- return ReplaceGermanUmlauts(NormalizeRegex.Replace(title, string.Empty).ToLower()).RemoveAccent();
- }
-
- public static string NormalizeEpisodeTitle(this string title)
- {
- title = SpecialEpisodeWordRegex.Replace(title, string.Empty);
- title = PunctuationRegex.Replace(title, " ");
- title = DuplicateSpacesRegex.Replace(title, " ");
-
- return title.Trim()
- .ToLower();
- }
-
- public static string NormalizeTitle(this string title)
- {
- title = WordDelimiterRegex.Replace(title, " ");
- title = PunctuationRegex.Replace(title, string.Empty);
- title = CommonWordRegex.Replace(title, string.Empty);
- title = DuplicateSpacesRegex.Replace(title, " ");
- title = SpecialCharRegex.Replace(title, string.Empty);
-
- return title.Trim().ToLower();
- }
-
- public static string SimplifyReleaseTitle(this string title)
- {
- return SimpleReleaseTitleRegex.Replace(title, string.Empty);
- }
-
- public static string ParseReleaseGroup(string title)
- {
- title = title.Trim();
- title = RemoveFileExtension(title);
- title = WebsitePrefixRegex.Replace(title);
-
- var animeMatch = AnimeReleaseGroupRegex.Match(title);
-
- if (animeMatch.Success)
- {
- return animeMatch.Groups["subgroup"].Value;
- }
-
- title = CleanReleaseGroupRegex.Replace(title);
-
- var matches = ReleaseGroupRegex.Matches(title);
-
- if (matches.Count != 0)
- {
- var group = matches.OfType().Last().Groups["releasegroup"].Value;
- int groupIsNumeric;
-
- if (int.TryParse(group, out groupIsNumeric))
- {
- return null;
- }
-
- return group;
- }
-
- return null;
- }
-
- public static string RemoveFileExtension(string title)
- {
- title = FileExtensionRegex.Replace(title, m =>
- {
- var extension = m.Value.ToLower();
-
- return m.Value;
- });
-
- return title;
- }
-
- private static ParsedMovieInfo ParseMovieMatchCollection(MatchCollection matchCollection)
- {
- if (!matchCollection[0].Groups["title"].Success || matchCollection[0].Groups["title"].Value == "(")
- {
- return null;
- }
-
- var movieName = matchCollection[0].Groups["title"].Value.Replace('_', ' ');
- movieName = RequestInfoRegex.Replace(movieName, "").Trim(' ');
-
- var parts = movieName.Split('.');
- movieName = "";
- int n = 0;
- bool previousAcronym = false;
- string nextPart = "";
- foreach (var part in parts)
- {
- if (parts.Length >= n + 2)
- {
- nextPart = parts[n + 1];
- }
- else
- {
- nextPart = "";
- }
-
- if (part.Length == 1 && part.ToLower() != "a" && !int.TryParse(part, out _) &&
- (previousAcronym || n < parts.Length - 1) &&
- (previousAcronym || nextPart.Length != 1 || !int.TryParse(nextPart, out _)))
- {
- movieName += part + ".";
- previousAcronym = true;
- }
- else if (part.ToLower() == "a" && (previousAcronym || nextPart.Length == 1))
- {
- movieName += part + ".";
- previousAcronym = true;
- }
- else if (part.ToLower() == "dr")
- {
- movieName += part + ".";
- previousAcronym = true;
- }
- else
- {
- if (previousAcronym)
- {
- movieName += " ";
- previousAcronym = false;
- }
-
- movieName += part + " ";
- }
-
- n++;
- }
-
- movieName = movieName.Trim(' ');
-
- int airYear;
- int.TryParse(matchCollection[0].Groups["year"].Value, out airYear);
-
- ParsedMovieInfo result;
-
- result = new ParsedMovieInfo { Year = airYear };
-
- if (matchCollection[0].Groups["edition"].Success)
- {
- result.Edition = matchCollection[0].Groups["edition"].Value.Replace(".", " ");
- }
-
- result.MovieTitle = movieName;
-
- Logger.Debug("Movie Parsed. {0}", result);
-
- return result;
- }
-
- private static bool ValidateBeforeParsing(string title)
- {
- if (title.ToLower().Contains("password") && title.ToLower().Contains("yenc"))
- {
- Logger.Debug("");
- return false;
- }
-
- if (!title.Any(char.IsLetterOrDigit))
- {
- return false;
- }
-
- var titleWithoutExtension = RemoveFileExtension(title);
-
- if (RejectHashedReleasesRegex.Any(v => v.IsMatch(titleWithoutExtension)))
- {
- Logger.Debug("Rejected Hashed Release Title: " + title);
- return false;
- }
-
- return true;
- }
-
- private static string GetSubGroup(MatchCollection matchCollection)
- {
- var subGroup = matchCollection[0].Groups["subgroup"];
-
- if (subGroup.Success)
- {
- return subGroup.Value;
- }
-
- return string.Empty;
- }
-
- private static string GetReleaseHash(MatchCollection matchCollection)
- {
- var hash = matchCollection[0].Groups["hash"];
-
- if (hash.Success)
- {
- var hashValue = hash.Value.Trim('[', ']');
-
- if (hashValue.Equals("1280x720"))
- {
- return string.Empty;
- }
-
- return hashValue;
- }
-
- return string.Empty;
- }
- }
-}
diff --git a/src/NzbDrone.Core/Parser/SceneChecker.cs b/src/NzbDrone.Core/Parser/SceneChecker.cs
deleted file mode 100644
index 459145c12..000000000
--- a/src/NzbDrone.Core/Parser/SceneChecker.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-namespace NzbDrone.Core.Parser
-{
- public static class SceneChecker
- {
- //This method should prefer false negatives over false positives.
- //It's better not to use a title that might be scene than to use one that isn't scene
- public static string GetSceneTitle(string title)
- {
- if (title == null)
- {
- return null;
- }
-
- if (!title.Contains("."))
- {
- return null;
- }
-
- if (title.Contains(" "))
- {
- return null;
- }
-
- var parsedTitle = Parser.ParseMovieTitle(title);
-
- if (parsedTitle == null ||
- parsedTitle.ReleaseGroup == null ||
- string.IsNullOrWhiteSpace(parsedTitle.MovieTitle) ||
- string.IsNullOrWhiteSpace(parsedTitle.ReleaseTitle))
- {
- return null;
- }
-
- return parsedTitle.ReleaseTitle;
- }
-
- public static bool IsSceneTitle(string title)
- {
- return GetSceneTitle(title) != null;
- }
- }
-}
diff --git a/src/Prowlarr.Api.V1/Indexers/IndexerModule.cs b/src/Prowlarr.Api.V1/Indexers/IndexerModule.cs
index ddabb8367..c9d9b59ec 100644
--- a/src/Prowlarr.Api.V1/Indexers/IndexerModule.cs
+++ b/src/Prowlarr.Api.V1/Indexers/IndexerModule.cs
@@ -1,5 +1,9 @@
+using System.Collections.Generic;
using Nancy;
+using Nancy.ModelBinding;
+using NzbDrone.Common.Extensions;
using NzbDrone.Core.Indexers;
+using NzbDrone.Core.IndexerSearch;
using Prowlarr.Http.REST;
namespace Prowlarr.Api.V1.Indexers
@@ -9,13 +13,19 @@ namespace Prowlarr.Api.V1.Indexers
public static readonly IndexerResourceMapper ResourceMapper = new IndexerResourceMapper();
private IIndexerFactory _indexerFactory { get; set; }
+ private ISearchForNzb _nzbSearchService { get; set; }
- public IndexerModule(IndexerFactory indexerFactory)
+ public IndexerModule(IndexerFactory indexerFactory, ISearchForNzb nzbSearchService)
: base(indexerFactory, "indexer", ResourceMapper)
{
_indexerFactory = indexerFactory;
+ _nzbSearchService = nzbSearchService;
- Get("{id}/newznab", x => GetNewznabResponse(x.id));
+ Get("{id}/newznab", x =>
+ {
+ var request = this.Bind();
+ return GetNewznabResponse(request);
+ });
}
protected override void Validate(IndexerDefinition definition, bool includeWarnings)
@@ -28,25 +38,36 @@ namespace Prowlarr.Api.V1.Indexers
base.Validate(definition, includeWarnings);
}
- private object GetNewznabResponse(int id)
+ private object GetNewznabResponse(NewznabRequest request)
{
- var requestType = Request.Query.t;
+ var requestType = request.t;
- if (!requestType.HasValue)
+ if (requestType.IsNullOrWhiteSpace())
{
throw new BadRequestException("Missing Function Parameter");
}
- if (requestType.Value == "caps")
+ var indexer = _indexerFactory.Get(request.id);
+
+ if (indexer == null)
{
- var indexer = _indexerFactory.GetInstance(_indexerFactory.Get(id));
- Response response = indexer.Capabilities.ToXml();
- response.ContentType = "application/rss+xml";
- return response;
+ throw new NotFoundException("Indexer Not Found");
}
- else
+
+ var indexerInstance = _indexerFactory.GetInstance(indexer);
+
+ switch (requestType)
{
- throw new BadRequestException("Function Not Available");
+ case "caps":
+ Response response = indexerInstance.GetCapabilities().ToXml();
+ response.ContentType = "application/rss+xml";
+ return response;
+ case "movie":
+ Response movieResponse = _nzbSearchService.Search(request, new List { indexer.Id }, true, false).ToXml();
+ movieResponse.ContentType = "application/rss+xml";
+ return movieResponse;
+ default:
+ throw new BadRequestException("Function Not Available");
}
}
}
diff --git a/src/Prowlarr.Api.V1/Indexers/IndexerResource.cs b/src/Prowlarr.Api.V1/Indexers/IndexerResource.cs
index 3601a0917..d53f7c81a 100644
--- a/src/Prowlarr.Api.V1/Indexers/IndexerResource.cs
+++ b/src/Prowlarr.Api.V1/Indexers/IndexerResource.cs
@@ -10,10 +10,6 @@ namespace Prowlarr.Api.V1.Indexers
public bool EnableInteractiveSearch { get; set; }
public bool SupportsRss { get; set; }
public bool SupportsSearch { get; set; }
- public bool SupportsMusic { get; set; }
- public bool SupportsTv { get; set; }
- public bool SupportsMovies { get; set; }
- public bool SupportsBooks { get; set; }
public DownloadProtocol Protocol { get; set; }
public IndexerPrivacy Privacy { get; set; }
public IndexerCapabilities Capabilities { get; set; }
diff --git a/src/Prowlarr.Api.V1/Search/SearchModule.cs b/src/Prowlarr.Api.V1/Search/SearchModule.cs
index 1e9bba7dd..95321035d 100644
--- a/src/Prowlarr.Api.V1/Search/SearchModule.cs
+++ b/src/Prowlarr.Api.V1/Search/SearchModule.cs
@@ -26,17 +26,26 @@ namespace Prowlarr.Api.V1.Search
{
if (Request.Query.query.HasValue)
{
- return GetSearchReleases(Request.Query.query);
+ var indexerIds = Request.Query.indexerIds.HasValue ? (List)Request.Query.indexerIds.split(',') : new List();
+
+ if (indexerIds.Count > 0)
+ {
+ return GetSearchReleases(Request.Query.query, indexerIds);
+ }
+ else
+ {
+ return GetSearchReleases(Request.Query.query, null);
+ }
}
return new List();
}
- private List GetSearchReleases(string query)
+ private List GetSearchReleases(string query, List indexerIds)
{
try
{
- var decisions = _nzbSearhService.MovieSearch(query, true, true);
+ var decisions = _nzbSearhService.Search(query, indexerIds, true, true);
return MapDecisions(decisions);
}