From 89b0b04e08ce171e177933a1c46ea9e3856b87da Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 14 Aug 2022 14:07:26 -0500 Subject: [PATCH] New: Custom Format Language Condition --- .../src/Activity/Blocklist/BlocklistRow.css | 2 +- .../src/Activity/Blocklist/BlocklistRow.js | 14 +- frontend/src/Activity/History/HistoryRow.js | 12 +- frontend/src/Activity/Queue/QueueRow.js | 12 +- .../AddNewSeries/AddNewSeriesModalContent.css | 6 - .../AddNewSeries/AddNewSeriesModalContent.js | 19 -- .../AddNewSeriesModalContentConnector.js | 7 +- .../ImportSeries/Import/ImportSeries.js | 6 +- .../Import/ImportSeriesConnector.js | 21 +- .../ImportSeries/Import/ImportSeriesFooter.js | 35 ---- .../Import/ImportSeriesFooterConnector.js | 4 - .../Import/ImportSeriesHeader.css | 3 +- .../ImportSeries/Import/ImportSeriesHeader.js | 12 -- .../ImportSeries/Import/ImportSeriesRow.css | 9 +- .../ImportSeries/Import/ImportSeriesRow.js | 15 -- .../ImportSeries/Import/ImportSeriesTable.js | 8 - .../Import/ImportSeriesTableConnector.js | 1 - frontend/src/Calendar/Agenda/AgendaEvent.js | 13 -- frontend/src/Calendar/Events/CalendarEvent.js | 14 -- frontend/src/Calendar/Legend/Legend.js | 2 +- .../Filter/Builder/FilterBuilderRow.js | 4 - ...geProfileFilterBuilderRowValueConnector.js | 28 --- .../src/Components/Form/FormInputGroup.js | 4 - .../LanguageProfileSelectInputConnector.js | 99 --------- .../Form/LanguageSelectInputConnector.js | 52 +++++ frontend/src/Components/Page/ErrorPage.js | 4 - frontend/src/Components/Page/PageConnector.js | 26 +-- frontend/src/Episode/EpisodeLanguage.js | 37 ---- frontend/src/Episode/EpisodeLanguages.js | 69 +++++++ .../src/Episode/History/EpisodeHistory.js | 4 +- .../src/Episode/History/EpisodeHistoryRow.js | 10 +- .../src/Episode/Summary/EpisodeFileRow.css | 2 +- .../src/Episode/Summary/EpisodeFileRow.js | 14 +- .../src/Episode/Summary/EpisodeSummary.js | 10 +- .../Summary/EpisodeSummaryConnector.js | 4 +- .../EpisodeFileLanguageConnector.js | 6 +- .../Helpers/Props/filterBuilderValueTypes.js | 1 - frontend/src/Helpers/Props/inputTypes.js | 4 +- .../InteractiveImportModalContent.js | 4 +- .../InteractiveImportModalContentConnector.js | 10 +- .../Interactive/InteractiveImportRow.css | 2 +- .../Interactive/InteractiveImportRow.js | 26 +-- .../SelectLanguageModalContentConnector.js | 27 +-- .../InteractiveSearch/InteractiveSearch.js | 2 +- .../InteractiveSearchRow.css | 4 +- .../InteractiveSearch/InteractiveSearchRow.js | 10 +- frontend/src/Series/Details/EpisodeRow.css | 2 +- frontend/src/Series/Details/EpisodeRow.js | 4 +- .../src/Series/Edit/EditSeriesModalContent.js | 17 -- .../Edit/EditSeriesModalContentConnector.js | 5 +- frontend/src/Series/Editor/SeriesEditor.js | 1 - .../src/Series/Editor/SeriesEditorFooter.js | 27 --- frontend/src/Series/Editor/SeriesEditorRow.js | 10 - .../Series/Editor/SeriesEditorRowConnector.js | 5 +- .../History/SeriesHistoryModalContent.js | 4 +- .../src/Series/History/SeriesHistoryRow.js | 10 +- .../Series/Index/Menus/SeriesIndexSortMenu.js | 9 - .../Index/Overview/SeriesIndexOverviews.js | 1 - .../Index/Posters/SeriesIndexPosters.js | 1 - .../Series/Index/SeriesIndexItemConnector.js | 4 - .../Series/Index/Table/SeriesIndexHeader.css | 3 +- .../src/Series/Index/Table/SeriesIndexRow.css | 3 +- .../src/Series/Index/Table/SeriesIndexRow.js | 13 -- .../Series/Index/Table/SeriesIndexTable.js | 1 - .../Series/Index/Table/hasGrowableColumns.js | 1 - .../EditImportListModalContent.css | 6 - .../ImportLists/EditImportListModalContent.js | 15 -- .../EditImportListModalContentConnector.js | 4 +- .../Language/EditLanguageProfileModal.js | 27 --- .../EditLanguageProfileModalConnector.js | 43 ---- .../EditLanguageProfileModalContent.css | 3 - .../EditLanguageProfileModalContent.js | 165 --------------- ...ditLanguageProfileModalContentConnector.js | 189 ------------------ .../Profiles/Language/LanguageProfile.css | 31 --- .../Profiles/Language/LanguageProfile.js | 147 -------------- .../Profiles/Language/LanguageProfileItem.css | 44 ---- .../Profiles/Language/LanguageProfileItem.js | 83 -------- .../LanguageProfileItemDragPreview.css | 4 - .../LanguageProfileItemDragPreview.js | 88 -------- .../LanguageProfileItemDragSource.css | 18 -- .../Language/LanguageProfileItemDragSource.js | 157 --------------- .../Language/LanguageProfileItems.css | 6 - .../Profiles/Language/LanguageProfileItems.js | 103 ---------- .../Language/LanguageProfileNameConnector.js | 31 --- .../Profiles/Language/LanguageProfiles.css | 21 -- .../Profiles/Language/LanguageProfiles.js | 107 ---------- .../Language/LanguageProfilesConnector.js | 69 ------- frontend/src/Settings/Profiles/Profiles.js | 2 - .../src/Settings/UI/UISettingsConnector.js | 22 +- .../Actions/Settings/languageProfiles.js | 97 --------- .../src/Store/Actions/Settings/languages.js | 48 +++++ .../src/Store/Actions/addSeriesActions.js | 1 - .../src/Store/Actions/blocklistActions.js | 4 +- frontend/src/Store/Actions/episodeActions.js | 4 +- .../src/Store/Actions/episodeFileActions.js | 2 +- frontend/src/Store/Actions/historyActions.js | 4 +- .../Store/Actions/interactiveImportActions.js | 2 +- frontend/src/Store/Actions/queueActions.js | 4 +- frontend/src/Store/Actions/seriesActions.js | 6 - .../src/Store/Actions/seriesEditorActions.js | 6 - .../src/Store/Actions/seriesIndexActions.js | 6 - frontend/src/Store/Actions/settingsActions.js | 10 +- frontend/src/Store/Actions/wantedActions.js | 4 +- .../createLanguageProfileSelector.js | 15 -- .../Selectors/createLanguagesSelector.js | 19 +- .../createSeriesLanguageProfileSelector.js | 16 -- frontend/src/Utilities/Series/getNewSeries.js | 2 - .../src/Wanted/CutoffUnmet/CutoffUnmetRow.css | 2 +- .../src/Wanted/CutoffUnmet/CutoffUnmetRow.js | 4 +- .../BlocklistRepositoryFixture.cs | 3 +- .../Datastore/DatabaseRelationshipFixture.cs | 10 +- .../161_fix_pending_releasesFixture.cs | 56 +++++- .../CutoffSpecificationFixture.cs | 116 +---------- .../LanguageSpecificationFixture.cs | 94 --------- .../PrioritizeDownloadDecisionFixture.cs | 25 +-- .../QueueSpecificationFixture.cs | 82 ++------ .../RssSync/DelaySpecificationFixture.cs | 17 +- .../RssSync/HistorySpecificationFixture.cs | 26 +-- .../UpgradeAllowedSpecificationFixture .cs | 134 +------------ .../UpgradeDiskSpecificationFixture.cs | 17 +- .../UpgradeSpecificationFixture.cs | 70 ------- .../HistoryTests/HistoryRepositoryFixture.cs | 9 +- .../HistoryTests/HistoryServiceFixture.cs | 10 +- .../CleanupOrphanedBlocklistFixture.cs | 6 +- .../CleanupOrphanedEpisodeFilesFixture.cs | 7 +- .../CleanupOrphanedHistoryItemsFixture.cs | 11 +- .../CleanupOrphanedMetadataFilesFixture.cs | 5 +- .../Languages/LanguageFixture.cs | 25 +-- .../LanguageProfileRepositoryFixture.cs | 31 --- .../LanguageProfileServiceFixture.cs | 105 ---------- .../Aggregators/AggregateLanguageFixture.cs | 126 ++++++++++-- .../EpisodeImport/GetSceneNameFixture.cs | 6 - .../ImportApprovedEpisodesFixture.cs | 6 - .../ImportDecisionMakerFixture.cs | 4 +- .../UpgradeSpecificationFixture.cs | 44 +--- .../MediaFiles/MediaFileRepositoryFixture.cs | 3 +- .../ParserTests/LanguageParserFixture.cs | 125 ++++++------ .../Profiles/QualityProfileServiceFixture.cs | 1 - .../SeriesStatisticsFixture.cs | 3 +- .../EpisodesRepositoryReadFixture.cs | 3 +- .../EpisodesWhereCutoffUnmetFixture.cs | 28 +-- .../EpisodesWithFilesFixture.cs | 4 +- .../SeriesRepositoryFixture.cs | 11 - src/NzbDrone.Core/Blocklisting/Blocklist.cs | 4 +- .../Blocklisting/BlocklistService.cs | 4 +- .../CustomFormatCalculationService.cs | 6 +- .../CustomFormatSpecificationBase.cs | 3 + .../ICustomFormatSpecification.cs | 3 + .../Specifications/LanguageSpecification.cs | 45 +++++ .../Specifications/RegexSpecificationBase.cs | 24 ++- .../Specifications/ResolutionSpecification.cs | 17 ++ .../Specifications/SizeSpecification.cs | 18 ++ .../Specifications/SourceSpecification.cs | 17 ++ ...175_language_profiles_to_custom_formats.cs | 37 ++++ src/NzbDrone.Core/Datastore/TableMapping.cs | 6 +- .../DownloadDecisionComparer.cs | 6 - .../DecisionEngine/DownloadDecisionMaker.cs | 2 +- .../Specifications/CutoffSpecification.cs | 7 +- .../Specifications/LanguageSpecification.cs | 36 ---- .../Specifications/QueueSpecification.cs | 23 +-- .../RssSync/DelaySpecification.cs | 8 +- .../RssSync/HistorySpecification.cs | 5 - .../Specifications/UpgradableSpecification.cs | 57 +----- .../UpgradeAllowedSpecification.cs | 12 +- .../UpgradeDiskSpecification.cs | 7 +- .../Download/Clients/Flood/Flood.cs | 4 +- .../Clients/Flood/Models/AdditionalTags.cs | 2 +- .../Download/Clients/Nzbget/NzbgetProxy.cs | 5 +- .../Download/DownloadFailedEvent.cs | 4 +- .../Download/DownloadIgnoredEvent.cs | 2 +- .../Download/FailedDownloadService.cs | 4 +- .../Download/IgnoredDownloadService.cs | 4 +- .../Download/Pending/PendingReleaseService.cs | 2 +- src/NzbDrone.Core/History/EpisodeHistory.cs | 4 +- src/NzbDrone.Core/History/HistoryService.cs | 12 +- .../EnsureValidLanguageProfileId.cs | 42 ---- .../ImportLists/ImportListDefinition.cs | 1 - .../ImportLists/ImportListSyncService.cs | 1 - .../Languages/LanguageComparer.cs | 27 --- .../Languages/LanguageFieldConverter.cs | 17 ++ .../Languages/LanguagesComparer.cs | 53 +++++ src/NzbDrone.Core/MediaFiles/EpisodeFile.cs | 2 +- .../Aggregators/AggregateLanguage.cs | 68 +++---- .../AugmentLanguageFromDownloadClientItem.cs | 34 ++++ .../Language/AugmentLanguageFromFileName.cs | 32 +++ .../Language/AugmentLanguageFromFolder.cs | 32 +++ .../Language/AugmentLanguageFromMediaInfo.cs | 40 ++++ .../Language/AugmentLanguageResult.cs | 18 ++ .../Augmenters/Language/Confidence.cs | 11 + .../Augmenters/Language/IAugmentLanguage.cs | 12 ++ .../EpisodeImport/ImportApprovedEpisodes.cs | 3 +- .../EpisodeImport/Manual/ManualImportFile.cs | 2 +- .../EpisodeImport/Manual/ManualImportItem.cs | 2 +- .../Manual/ManualImportService.cs | 22 +- .../Specifications/UpgradeSpecification.cs | 9 - src/NzbDrone.Core/Parser/LanguageParser.cs | 107 +++++----- .../Parser/Model/LocalEpisode.cs | 3 +- .../Parser/Model/ParsedEpisodeInfo.cs | 3 +- src/NzbDrone.Core/Parser/Parser.cs | 4 +- src/NzbDrone.Core/Parser/ParsingService.cs | 2 +- .../Profiles/Languages/LanguageProfile.cs | 25 --- .../LanguageProfileInUseException.cs | 12 -- .../Profiles/Languages/LanguageProfileItem.cs | 11 - .../Languages/LanguageProfileRepository.cs | 23 --- .../Languages/LanguageProfileService.cs | 121 ----------- src/NzbDrone.Core/Queue/Queue.cs | 2 +- src/NzbDrone.Core/Queue/QueueService.cs | 2 +- src/NzbDrone.Core/Tv/EpisodeCutoffService.cs | 20 +- src/NzbDrone.Core/Tv/EpisodeRepository.cs | 20 +- src/NzbDrone.Core/Tv/Series.cs | 4 - .../LanguageProfileExistsValidator.cs | 26 --- .../ApiTests/EpisodeFixture.cs | 1 - .../ApiTests/SeriesEditorFixture.cs | 3 +- .../ApiTests/SeriesFixture.cs | 3 - .../IntegrationTestBase.cs | 1 - .../Blocklist/BlocklistResource.cs | 4 +- .../EpisodeFiles/EpisodeFileController.cs | 8 +- .../EpisodeFiles/EpisodeFileListResource.cs | 4 +- .../EpisodeFiles/EpisodeFileResource.cs | 9 +- .../History/HistoryController.cs | 1 - src/Sonarr.Api.V3/History/HistoryResource.cs | 5 +- .../ImportLists/ImportListController.cs | 4 +- .../ImportLists/ImportListResource.cs | 3 - .../Indexers/ReleaseController.cs | 4 +- .../Indexers/ReleaseControllerBase.cs | 7 +- .../Indexers/ReleasePushController.cs | 4 +- src/Sonarr.Api.V3/Indexers/ReleaseResource.cs | 4 +- .../ManualImport/ManualImportController.cs | 6 +- .../ManualImportReprocessResource.cs | 2 +- .../ManualImport/ManualImportResource.cs | 4 +- .../Language/LanguageProfileController.cs | 64 ------ .../Language/LanguageProfileResource.cs | 91 --------- .../LanguageProfileSchemaController.cs | 26 --- .../Profiles/Language/LanguageValidator.cs | 42 ---- .../Profiles/Languages/LanguageController.cs | 38 ++++ .../Profiles/Languages/LanguageResource.cs | 13 ++ src/Sonarr.Api.V3/Queue/QueueController.cs | 14 +- src/Sonarr.Api.V3/Queue/QueueResource.cs | 4 +- src/Sonarr.Api.V3/Series/SeriesController.cs | 2 - .../Series/SeriesEditorController.cs | 5 - .../Series/SeriesEditorResource.cs | 1 - src/Sonarr.Api.V3/Series/SeriesResource.cs | 3 - src/Sonarr.Http/ClientSchema/SchemaBuilder.cs | 55 +++-- 243 files changed, 1345 insertions(+), 3956 deletions(-) delete mode 100644 frontend/src/Components/Filter/Builder/LanguageProfileFilterBuilderRowValueConnector.js delete mode 100644 frontend/src/Components/Form/LanguageProfileSelectInputConnector.js create mode 100644 frontend/src/Components/Form/LanguageSelectInputConnector.js delete mode 100644 frontend/src/Episode/EpisodeLanguage.js create mode 100644 frontend/src/Episode/EpisodeLanguages.js delete mode 100644 frontend/src/Settings/Profiles/Language/EditLanguageProfileModal.js delete mode 100644 frontend/src/Settings/Profiles/Language/EditLanguageProfileModalConnector.js delete mode 100644 frontend/src/Settings/Profiles/Language/EditLanguageProfileModalContent.css delete mode 100644 frontend/src/Settings/Profiles/Language/EditLanguageProfileModalContent.js delete mode 100644 frontend/src/Settings/Profiles/Language/EditLanguageProfileModalContentConnector.js delete mode 100644 frontend/src/Settings/Profiles/Language/LanguageProfile.css delete mode 100644 frontend/src/Settings/Profiles/Language/LanguageProfile.js delete mode 100644 frontend/src/Settings/Profiles/Language/LanguageProfileItem.css delete mode 100644 frontend/src/Settings/Profiles/Language/LanguageProfileItem.js delete mode 100644 frontend/src/Settings/Profiles/Language/LanguageProfileItemDragPreview.css delete mode 100644 frontend/src/Settings/Profiles/Language/LanguageProfileItemDragPreview.js delete mode 100644 frontend/src/Settings/Profiles/Language/LanguageProfileItemDragSource.css delete mode 100644 frontend/src/Settings/Profiles/Language/LanguageProfileItemDragSource.js delete mode 100644 frontend/src/Settings/Profiles/Language/LanguageProfileItems.css delete mode 100644 frontend/src/Settings/Profiles/Language/LanguageProfileItems.js delete mode 100644 frontend/src/Settings/Profiles/Language/LanguageProfileNameConnector.js delete mode 100644 frontend/src/Settings/Profiles/Language/LanguageProfiles.css delete mode 100644 frontend/src/Settings/Profiles/Language/LanguageProfiles.js delete mode 100644 frontend/src/Settings/Profiles/Language/LanguageProfilesConnector.js delete mode 100644 frontend/src/Store/Actions/Settings/languageProfiles.js create mode 100644 frontend/src/Store/Actions/Settings/languages.js delete mode 100644 frontend/src/Store/Selectors/createLanguageProfileSelector.js delete mode 100644 frontend/src/Store/Selectors/createSeriesLanguageProfileSelector.js delete mode 100644 src/NzbDrone.Core.Test/DecisionEngineTests/LanguageSpecificationFixture.cs delete mode 100644 src/NzbDrone.Core.Test/Languages/LanguageProfileRepositoryFixture.cs delete mode 100644 src/NzbDrone.Core.Test/Languages/LanguageProfileServiceFixture.cs create mode 100644 src/NzbDrone.Core/CustomFormats/Specifications/LanguageSpecification.cs create mode 100644 src/NzbDrone.Core/Datastore/Migration/175_language_profiles_to_custom_formats.cs delete mode 100644 src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs delete mode 100644 src/NzbDrone.Core/Housekeeping/Housekeepers/EnsureValidLanguageProfileId.cs delete mode 100644 src/NzbDrone.Core/Languages/LanguageComparer.cs create mode 100644 src/NzbDrone.Core/Languages/LanguageFieldConverter.cs create mode 100644 src/NzbDrone.Core/Languages/LanguagesComparer.cs create mode 100644 src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromDownloadClientItem.cs create mode 100644 src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromFileName.cs create mode 100644 src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromFolder.cs create mode 100644 src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromMediaInfo.cs create mode 100644 src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageResult.cs create mode 100644 src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/Confidence.cs create mode 100644 src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/IAugmentLanguage.cs delete mode 100644 src/NzbDrone.Core/Profiles/Languages/LanguageProfile.cs delete mode 100644 src/NzbDrone.Core/Profiles/Languages/LanguageProfileInUseException.cs delete mode 100644 src/NzbDrone.Core/Profiles/Languages/LanguageProfileItem.cs delete mode 100644 src/NzbDrone.Core/Profiles/Languages/LanguageProfileRepository.cs delete mode 100644 src/NzbDrone.Core/Profiles/Languages/LanguageProfileService.cs delete mode 100644 src/NzbDrone.Core/Validation/LanguageProfileExistsValidator.cs delete mode 100644 src/Sonarr.Api.V3/Profiles/Language/LanguageProfileController.cs delete mode 100644 src/Sonarr.Api.V3/Profiles/Language/LanguageProfileResource.cs delete mode 100644 src/Sonarr.Api.V3/Profiles/Language/LanguageProfileSchemaController.cs delete mode 100644 src/Sonarr.Api.V3/Profiles/Language/LanguageValidator.cs create mode 100644 src/Sonarr.Api.V3/Profiles/Languages/LanguageController.cs create mode 100644 src/Sonarr.Api.V3/Profiles/Languages/LanguageResource.cs diff --git a/frontend/src/Activity/Blocklist/BlocklistRow.css b/frontend/src/Activity/Blocklist/BlocklistRow.css index c7d31a886..aa008a6ce 100644 --- a/frontend/src/Activity/Blocklist/BlocklistRow.css +++ b/frontend/src/Activity/Blocklist/BlocklistRow.css @@ -1,4 +1,4 @@ -.language, +.languages, .quality { composes: cell from '~Components/Table/Cells/TableRowCell.css'; diff --git a/frontend/src/Activity/Blocklist/BlocklistRow.js b/frontend/src/Activity/Blocklist/BlocklistRow.js index 571ba5d8d..30bb36844 100644 --- a/frontend/src/Activity/Blocklist/BlocklistRow.js +++ b/frontend/src/Activity/Blocklist/BlocklistRow.js @@ -6,7 +6,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableSelectCell from 'Components/Table/Cells/TableSelectCell'; import TableRow from 'Components/Table/TableRow'; import EpisodeFormats from 'Episode/EpisodeFormats'; -import EpisodeLanguage from 'Episode/EpisodeLanguage'; +import EpisodeLanguages from 'Episode/EpisodeLanguages'; import EpisodeQuality from 'Episode/EpisodeQuality'; import { icons, kinds } from 'Helpers/Props'; import SeriesTitleLink from 'Series/SeriesTitleLink'; @@ -45,7 +45,7 @@ class BlocklistRow extends Component { id, series, sourceTitle, - language, + languages, quality, customFormats, date, @@ -96,14 +96,14 @@ class BlocklistRow extends Component { ); } - if (name === 'language') { + if (name === 'languages') { return ( - ); @@ -195,7 +195,7 @@ BlocklistRow.propTypes = { id: PropTypes.number.isRequired, series: PropTypes.object.isRequired, sourceTitle: PropTypes.string.isRequired, - language: PropTypes.object.isRequired, + languages: PropTypes.arrayOf(PropTypes.object).isRequired, quality: PropTypes.object.isRequired, customFormats: PropTypes.arrayOf(PropTypes.object).isRequired, date: PropTypes.string.isRequired, diff --git a/frontend/src/Activity/History/HistoryRow.js b/frontend/src/Activity/History/HistoryRow.js index 8ad1c07f7..0c566b4aa 100644 --- a/frontend/src/Activity/History/HistoryRow.js +++ b/frontend/src/Activity/History/HistoryRow.js @@ -6,7 +6,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRow from 'Components/Table/TableRow'; import episodeEntities from 'Episode/episodeEntities'; import EpisodeFormats from 'Episode/EpisodeFormats'; -import EpisodeLanguage from 'Episode/EpisodeLanguage'; +import EpisodeLanguages from 'Episode/EpisodeLanguages'; import EpisodeQuality from 'Episode/EpisodeQuality'; import EpisodeTitleLink from 'Episode/EpisodeTitleLink'; import SeasonEpisodeNumber from 'Episode/SeasonEpisodeNumber'; @@ -59,7 +59,7 @@ class HistoryRow extends Component { episodeId, series, episode, - language, + languages, languageCutoffNotMet, quality, customFormats, @@ -144,11 +144,11 @@ class HistoryRow extends Component { ); } - if (name === 'language') { + if (name === 'languages') { return ( - @@ -278,7 +278,7 @@ HistoryRow.propTypes = { episodeId: PropTypes.number, series: PropTypes.object.isRequired, episode: PropTypes.object, - language: PropTypes.object.isRequired, + languages: PropTypes.arrayOf(PropTypes.object).isRequired, languageCutoffNotMet: PropTypes.bool.isRequired, quality: PropTypes.object.isRequired, customFormats: PropTypes.arrayOf(PropTypes.object), diff --git a/frontend/src/Activity/Queue/QueueRow.js b/frontend/src/Activity/Queue/QueueRow.js index 67b8b9b3c..aba9ce3ea 100644 --- a/frontend/src/Activity/Queue/QueueRow.js +++ b/frontend/src/Activity/Queue/QueueRow.js @@ -9,7 +9,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableSelectCell from 'Components/Table/Cells/TableSelectCell'; import TableRow from 'Components/Table/TableRow'; import EpisodeFormats from 'Episode/EpisodeFormats'; -import EpisodeLanguage from 'Episode/EpisodeLanguage'; +import EpisodeLanguages from 'Episode/EpisodeLanguages'; import EpisodeQuality from 'Episode/EpisodeQuality'; import EpisodeTitleLink from 'Episode/EpisodeTitleLink'; import SeasonEpisodeNumber from 'Episode/SeasonEpisodeNumber'; @@ -88,7 +88,7 @@ class QueueRow extends Component { errorMessage, series, episode, - language, + languages, quality, customFormats, protocol, @@ -225,11 +225,11 @@ class QueueRow extends Component { ); } - if (name === 'language') { + if (name === 'languages') { return ( - ); @@ -410,7 +410,7 @@ QueueRow.propTypes = { errorMessage: PropTypes.string, series: PropTypes.object, episode: PropTypes.object, - language: PropTypes.object.isRequired, + languages: PropTypes.arrayOf(PropTypes.object).isRequired, quality: PropTypes.object.isRequired, customFormats: PropTypes.arrayOf(PropTypes.object), protocol: PropTypes.string.isRequired, diff --git a/frontend/src/AddSeries/AddNewSeries/AddNewSeriesModalContent.css b/frontend/src/AddSeries/AddNewSeries/AddNewSeriesModalContent.css index 2311e70bf..5da3d9b96 100644 --- a/frontend/src/AddSeries/AddNewSeries/AddNewSeriesModalContent.css +++ b/frontend/src/AddSeries/AddNewSeries/AddNewSeriesModalContent.css @@ -57,12 +57,6 @@ composes: button from '~Components/Link/SpinnerButton.css'; } -.hideLanguageProfile { - composes: group from '~Components/Form/FormGroup.css'; - - display: none; -} - @media only screen and (max-width: $breakpointSmall) { .modalFooter { display: block; diff --git a/frontend/src/AddSeries/AddNewSeries/AddNewSeriesModalContent.js b/frontend/src/AddSeries/AddNewSeries/AddNewSeriesModalContent.js index a19444fe1..1c8914e6b 100644 --- a/frontend/src/AddSeries/AddNewSeries/AddNewSeriesModalContent.js +++ b/frontend/src/AddSeries/AddNewSeries/AddNewSeriesModalContent.js @@ -47,10 +47,6 @@ class AddNewSeriesModalContent extends Component { this.props.onInputChange({ name: 'qualityProfileId', value: parseInt(value) }); }; - onLanguageProfileIdChange = ({ value }) => { - this.props.onInputChange({ name: 'languageProfileId', value: parseInt(value) }); - }; - onAddSeriesPress = () => { const { seriesType @@ -74,14 +70,12 @@ class AddNewSeriesModalContent extends Component { rootFolderPath, monitor, qualityProfileId, - languageProfileId, seriesType, seasonFolder, searchForMissingEpisodes, searchForCutoffUnmetEpisodes, folder, tags, - showLanguageProfile, isSmallScreen, isWindows, onModalClose, @@ -180,17 +174,6 @@ class AddNewSeriesModalContent extends Component { /> - - Language Profile - - - - Series Type @@ -299,14 +282,12 @@ AddNewSeriesModalContent.propTypes = { rootFolderPath: PropTypes.object, monitor: PropTypes.object.isRequired, qualityProfileId: PropTypes.object, - languageProfileId: PropTypes.object, seriesType: PropTypes.object.isRequired, seasonFolder: PropTypes.object.isRequired, searchForMissingEpisodes: PropTypes.object.isRequired, searchForCutoffUnmetEpisodes: PropTypes.object.isRequired, folder: PropTypes.string.isRequired, tags: PropTypes.object.isRequired, - showLanguageProfile: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired, isWindows: PropTypes.bool.isRequired, onModalClose: PropTypes.func.isRequired, diff --git a/frontend/src/AddSeries/AddNewSeries/AddNewSeriesModalContentConnector.js b/frontend/src/AddSeries/AddNewSeries/AddNewSeriesModalContentConnector.js index 705b0e3df..f40b3a2f1 100644 --- a/frontend/src/AddSeries/AddNewSeries/AddNewSeriesModalContentConnector.js +++ b/frontend/src/AddSeries/AddNewSeries/AddNewSeriesModalContentConnector.js @@ -11,10 +11,9 @@ import AddNewSeriesModalContent from './AddNewSeriesModalContent'; function createMapStateToProps() { return createSelector( (state) => state.addSeries, - (state) => state.settings.languageProfiles, createDimensionsSelector(), createSystemStatusSelector(), - (addSeriesState, languageProfiles, dimensions, systemStatus) => { + (addSeriesState, dimensions, systemStatus) => { const { isAdding, addError, @@ -30,7 +29,6 @@ function createMapStateToProps() { return { isAdding, addError, - showLanguageProfile: languageProfiles.items.length > 1, isSmallScreen: dimensions.isSmallScreen, validationErrors, validationWarnings, @@ -61,7 +59,6 @@ class AddNewSeriesModalContentConnector extends Component { rootFolderPath, monitor, qualityProfileId, - languageProfileId, seasonFolder, searchForMissingEpisodes, searchForCutoffUnmetEpisodes, @@ -73,7 +70,6 @@ class AddNewSeriesModalContentConnector extends Component { rootFolderPath: rootFolderPath.value, monitor: monitor.value, qualityProfileId: qualityProfileId.value, - languageProfileId: languageProfileId.value, seriesType, seasonFolder: seasonFolder.value, searchForMissingEpisodes: searchForMissingEpisodes.value, @@ -101,7 +97,6 @@ AddNewSeriesModalContentConnector.propTypes = { rootFolderPath: PropTypes.object, monitor: PropTypes.object.isRequired, qualityProfileId: PropTypes.object, - languageProfileId: PropTypes.object, seriesType: PropTypes.object.isRequired, seasonFolder: PropTypes.object.isRequired, searchForMissingEpisodes: PropTypes.object.isRequired, diff --git a/frontend/src/AddSeries/ImportSeries/Import/ImportSeries.js b/frontend/src/AddSeries/ImportSeries/Import/ImportSeries.js index 4b8d70d3e..8580d47af 100644 --- a/frontend/src/AddSeries/ImportSeries/Import/ImportSeries.js +++ b/frontend/src/AddSeries/ImportSeries/Import/ImportSeries.js @@ -84,8 +84,7 @@ class ImportSeries extends Component { rootFoldersFetching, rootFoldersPopulated, rootFoldersError, - unmappedFolders, - showLanguageProfile + unmappedFolders } = this.props; const { @@ -135,7 +134,6 @@ class ImportSeries extends Component { allUnselected={allUnselected} selectedState={selectedState} scroller={scroller} - showLanguageProfile={showLanguageProfile} onSelectAllChange={this.onSelectAllChange} onSelectedChange={this.onSelectedChange} onRemoveSelectedStateItem={this.onRemoveSelectedStateItem} @@ -150,7 +148,6 @@ class ImportSeries extends Component { !!unmappedFolders.length ? : @@ -169,7 +166,6 @@ ImportSeries.propTypes = { rootFoldersError: PropTypes.object, unmappedFolders: PropTypes.arrayOf(PropTypes.object), items: PropTypes.arrayOf(PropTypes.object), - showLanguageProfile: PropTypes.bool.isRequired, onInputChange: PropTypes.func.isRequired, onImportPress: PropTypes.func.isRequired }; diff --git a/frontend/src/AddSeries/ImportSeries/Import/ImportSeriesConnector.js b/frontend/src/AddSeries/ImportSeries/Import/ImportSeriesConnector.js index 92b99cded..50436ba88 100644 --- a/frontend/src/AddSeries/ImportSeries/Import/ImportSeriesConnector.js +++ b/frontend/src/AddSeries/ImportSeries/Import/ImportSeriesConnector.js @@ -16,14 +16,12 @@ function createMapStateToProps() { (state) => state.addSeries, (state) => state.importSeries, (state) => state.settings.qualityProfiles, - (state) => state.settings.languageProfiles, ( match, rootFolders, addSeries, importSeriesState, - qualityProfiles, - languageProfiles + qualityProfiles ) => { const { isFetching: rootFoldersFetching, @@ -40,10 +38,7 @@ function createMapStateToProps() { rootFoldersPopulated, rootFoldersError, qualityProfiles: qualityProfiles.items, - languageProfiles: languageProfiles.items, - showLanguageProfile: languageProfiles.items.length > 1, - defaultQualityProfileId: addSeries.defaults.qualityProfileId, - defaultLanguageProfileId: addSeries.defaults.languageProfileId + defaultQualityProfileId: addSeries.defaults.qualityProfileId }; if (items.length) { @@ -78,9 +73,7 @@ class ImportSeriesConnector extends Component { const { rootFolderId, qualityProfiles, - languageProfiles, defaultQualityProfileId, - defaultLanguageProfileId, dispatchFetchRootFolders, dispatchSetAddSeriesDefault } = this.props; @@ -98,14 +91,6 @@ class ImportSeriesConnector extends Component { setDefaultPayload.qualityProfileId = qualityProfiles[0].id; } - if ( - !defaultLanguageProfileId || - !languageProfiles.some((p) => p.id === defaultLanguageProfileId) - ) { - setDefaults = true; - setDefaultPayload.languageProfileId = languageProfiles[0].id; - } - if (setDefaults) { dispatchSetAddSeriesDefault(setDefaultPayload); } @@ -157,9 +142,7 @@ ImportSeriesConnector.propTypes = { rootFoldersFetching: PropTypes.bool.isRequired, rootFoldersPopulated: PropTypes.bool.isRequired, qualityProfiles: PropTypes.arrayOf(PropTypes.object).isRequired, - languageProfiles: PropTypes.arrayOf(PropTypes.object).isRequired, defaultQualityProfileId: PropTypes.number.isRequired, - defaultLanguageProfileId: PropTypes.number.isRequired, dispatchSetImportSeriesValue: PropTypes.func.isRequired, dispatchImportSeries: PropTypes.func.isRequired, dispatchClearImportSeries: PropTypes.func.isRequired, diff --git a/frontend/src/AddSeries/ImportSeries/Import/ImportSeriesFooter.js b/frontend/src/AddSeries/ImportSeries/Import/ImportSeriesFooter.js index 84c881a2f..a4b530451 100644 --- a/frontend/src/AddSeries/ImportSeries/Import/ImportSeriesFooter.js +++ b/frontend/src/AddSeries/ImportSeries/Import/ImportSeriesFooter.js @@ -25,7 +25,6 @@ class ImportSeriesFooter extends Component { const { defaultMonitor, defaultQualityProfileId, - defaultLanguageProfileId, defaultSeasonFolder, defaultSeriesType } = props; @@ -33,7 +32,6 @@ class ImportSeriesFooter extends Component { this.state = { monitor: defaultMonitor, qualityProfileId: defaultQualityProfileId, - languageProfileId: defaultLanguageProfileId, seriesType: defaultSeriesType, seasonFolder: defaultSeasonFolder }; @@ -43,12 +41,10 @@ class ImportSeriesFooter extends Component { const { defaultMonitor, defaultQualityProfileId, - defaultLanguageProfileId, defaultSeriesType, defaultSeasonFolder, isMonitorMixed, isQualityProfileIdMixed, - isLanguageProfileIdMixed, isSeriesTypeMixed, isSeasonFolderMixed } = this.props; @@ -56,7 +52,6 @@ class ImportSeriesFooter extends Component { const { monitor, qualityProfileId, - languageProfileId, seriesType, seasonFolder } = this.state; @@ -75,12 +70,6 @@ class ImportSeriesFooter extends Component { newState.qualityProfileId = defaultQualityProfileId; } - if (isLanguageProfileIdMixed && languageProfileId !== MIXED) { - newState.languageProfileId = MIXED; - } else if (!isLanguageProfileIdMixed && languageProfileId !== defaultLanguageProfileId) { - newState.languageProfileId = defaultLanguageProfileId; - } - if (isSeriesTypeMixed && seriesType !== MIXED) { newState.seriesType = MIXED; } else if (!isSeriesTypeMixed && seriesType !== defaultSeriesType) { @@ -116,10 +105,8 @@ class ImportSeriesFooter extends Component { isLookingUpSeries, isMonitorMixed, isQualityProfileIdMixed, - isLanguageProfileIdMixed, isSeriesTypeMixed, hasUnsearchedItems, - showLanguageProfile, importError, onImportPress, onLookupPress, @@ -129,7 +116,6 @@ class ImportSeriesFooter extends Component { const { monitor, qualityProfileId, - languageProfileId, seriesType, seasonFolder } = this.state; @@ -166,24 +152,6 @@ class ImportSeriesFooter extends Component { /> - { - showLanguageProfile && -
-
- Language Profile -
- - -
- } -
Series Type @@ -308,16 +276,13 @@ ImportSeriesFooter.propTypes = { isLookingUpSeries: PropTypes.bool.isRequired, defaultMonitor: PropTypes.string.isRequired, defaultQualityProfileId: PropTypes.number, - defaultLanguageProfileId: PropTypes.number, defaultSeriesType: PropTypes.string.isRequired, defaultSeasonFolder: PropTypes.bool.isRequired, isMonitorMixed: PropTypes.bool.isRequired, isQualityProfileIdMixed: PropTypes.bool.isRequired, - isLanguageProfileIdMixed: PropTypes.bool.isRequired, isSeriesTypeMixed: PropTypes.bool.isRequired, isSeasonFolderMixed: PropTypes.bool.isRequired, hasUnsearchedItems: PropTypes.bool.isRequired, - showLanguageProfile: PropTypes.bool.isRequired, importError: PropTypes.object, onInputChange: PropTypes.func.isRequired, onImportPress: PropTypes.func.isRequired, diff --git a/frontend/src/AddSeries/ImportSeries/Import/ImportSeriesFooterConnector.js b/frontend/src/AddSeries/ImportSeries/Import/ImportSeriesFooterConnector.js index d43fc6553..12a90aae7 100644 --- a/frontend/src/AddSeries/ImportSeries/Import/ImportSeriesFooterConnector.js +++ b/frontend/src/AddSeries/ImportSeries/Import/ImportSeriesFooterConnector.js @@ -19,7 +19,6 @@ function createMapStateToProps() { const { monitor: defaultMonitor, qualityProfileId: defaultQualityProfileId, - languageProfileId: defaultLanguageProfileId, seriesType: defaultSeriesType, seasonFolder: defaultSeasonFolder } = addSeries.defaults; @@ -33,7 +32,6 @@ function createMapStateToProps() { const isMonitorMixed = isMixed(items, selectedIds, defaultMonitor, 'monitor'); const isQualityProfileIdMixed = isMixed(items, selectedIds, defaultQualityProfileId, 'qualityProfileId'); - const isLanguageProfileIdMixed = isMixed(items, selectedIds, defaultLanguageProfileId, 'languageProfileId'); const isSeriesTypeMixed = isMixed(items, selectedIds, defaultSeriesType, 'seriesType'); const isSeasonFolderMixed = isMixed(items, selectedIds, defaultSeasonFolder, 'seasonFolder'); const hasUnsearchedItems = !isLookingUpSeries && items.some((item) => !item.isPopulated); @@ -44,12 +42,10 @@ function createMapStateToProps() { isImporting, defaultMonitor, defaultQualityProfileId, - defaultLanguageProfileId, defaultSeriesType, defaultSeasonFolder, isMonitorMixed, isQualityProfileIdMixed, - isLanguageProfileIdMixed, isSeriesTypeMixed, isSeasonFolderMixed, importError, diff --git a/frontend/src/AddSeries/ImportSeries/Import/ImportSeriesHeader.css b/frontend/src/AddSeries/ImportSeries/Import/ImportSeriesHeader.css index 00284e107..df5214bdd 100644 --- a/frontend/src/AddSeries/ImportSeries/Import/ImportSeriesHeader.css +++ b/frontend/src/AddSeries/ImportSeries/Import/ImportSeriesHeader.css @@ -11,8 +11,7 @@ min-width: 185px; } -.qualityProfile, -.languageProfile { +.qualityProfile { composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css'; flex: 0 1 250px; diff --git a/frontend/src/AddSeries/ImportSeries/Import/ImportSeriesHeader.js b/frontend/src/AddSeries/ImportSeries/Import/ImportSeriesHeader.js index d9af38560..d6ce86bad 100644 --- a/frontend/src/AddSeries/ImportSeries/Import/ImportSeriesHeader.js +++ b/frontend/src/AddSeries/ImportSeries/Import/ImportSeriesHeader.js @@ -12,7 +12,6 @@ import styles from './ImportSeriesHeader.css'; function ImportSeriesHeader(props) { const { - showLanguageProfile, allSelected, allUnselected, onSelectAllChange @@ -59,16 +58,6 @@ function ImportSeriesHeader(props) { Quality Profile - { - showLanguageProfile && - - Language Profile - - } - - - - - } - { - showCutoffUnmetIcon && - !!episodeFile && - episodeFile.languageCutoffNotMet && - !episodeFile.qualityCutoffNotMet && - - } - { episodeNumber === 1 && seasonNumber > 0 && : - null - } - { episodeNumber === 1 && seasonNumber > 0 ? ); } diff --git a/frontend/src/Components/Filter/Builder/FilterBuilderRow.js b/frontend/src/Components/Filter/Builder/FilterBuilderRow.js index f6d0a8b6d..516ddf193 100644 --- a/frontend/src/Components/Filter/Builder/FilterBuilderRow.js +++ b/frontend/src/Components/Filter/Builder/FilterBuilderRow.js @@ -7,7 +7,6 @@ import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue'; import DateFilterBuilderRowValue from './DateFilterBuilderRowValue'; import FilterBuilderRowValueConnector from './FilterBuilderRowValueConnector'; import IndexerFilterBuilderRowValueConnector from './IndexerFilterBuilderRowValueConnector'; -import LanguageProfileFilterBuilderRowValueConnector from './LanguageProfileFilterBuilderRowValueConnector'; import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue'; import QualityFilterBuilderRowValueConnector from './QualityFilterBuilderRowValueConnector'; import QualityProfileFilterBuilderRowValueConnector from './QualityProfileFilterBuilderRowValueConnector'; @@ -61,9 +60,6 @@ function getRowValueConnector(selectedFilterBuilderProp) { case filterBuilderValueTypes.INDEXER: return IndexerFilterBuilderRowValueConnector; - case filterBuilderValueTypes.LANGUAGE_PROFILE: - return LanguageProfileFilterBuilderRowValueConnector; - case filterBuilderValueTypes.PROTOCOL: return ProtocolFilterBuilderRowValue; diff --git a/frontend/src/Components/Filter/Builder/LanguageProfileFilterBuilderRowValueConnector.js b/frontend/src/Components/Filter/Builder/LanguageProfileFilterBuilderRowValueConnector.js deleted file mode 100644 index 31b1e952a..000000000 --- a/frontend/src/Components/Filter/Builder/LanguageProfileFilterBuilderRowValueConnector.js +++ /dev/null @@ -1,28 +0,0 @@ -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import FilterBuilderRowValue from './FilterBuilderRowValue'; - -function createMapStateToProps() { - return createSelector( - (state) => state.settings.languageProfiles, - (languageProfiles) => { - const tagList = languageProfiles.items.map((languageProfile) => { - const { - id, - name - } = languageProfile; - - return { - id, - name - }; - }); - - return { - tagList - }; - } - ); -} - -export default connect(createMapStateToProps)(FilterBuilderRowValue); diff --git a/frontend/src/Components/Form/FormInputGroup.js b/frontend/src/Components/Form/FormInputGroup.js index 5951520d5..94780aba2 100644 --- a/frontend/src/Components/Form/FormInputGroup.js +++ b/frontend/src/Components/Form/FormInputGroup.js @@ -12,7 +12,6 @@ import EnhancedSelectInputConnector from './EnhancedSelectInputConnector'; import FormInputHelpText from './FormInputHelpText'; import IndexerSelectInputConnector from './IndexerSelectInputConnector'; import KeyValueListInput from './KeyValueListInput'; -import LanguageProfileSelectInputConnector from './LanguageProfileSelectInputConnector'; import MonitorEpisodesSelectInput from './MonitorEpisodesSelectInput'; import NumberInput from './NumberInput'; import OAuthInputConnector from './OAuthInputConnector'; @@ -64,9 +63,6 @@ function getComponent(type) { case inputTypes.QUALITY_PROFILE_SELECT: return QualityProfileSelectInputConnector; - case inputTypes.LANGUAGE_PROFILE_SELECT: - return LanguageProfileSelectInputConnector; - case inputTypes.INDEXER_SELECT: return IndexerSelectInputConnector; diff --git a/frontend/src/Components/Form/LanguageProfileSelectInputConnector.js b/frontend/src/Components/Form/LanguageProfileSelectInputConnector.js deleted file mode 100644 index 01c00969d..000000000 --- a/frontend/src/Components/Form/LanguageProfileSelectInputConnector.js +++ /dev/null @@ -1,99 +0,0 @@ -import _ from 'lodash'; -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector'; -import sortByName from 'Utilities/Array/sortByName'; -import SelectInput from './SelectInput'; - -function createMapStateToProps() { - return createSelector( - createSortedSectionSelector('settings.languageProfiles', sortByName), - (state, { includeNoChange }) => includeNoChange, - (state, { includeMixed }) => includeMixed, - (languageProfiles, includeNoChange, includeMixed) => { - const values = _.map(languageProfiles.items, (languageProfile) => { - return { - key: languageProfile.id, - value: languageProfile.name - }; - }); - - if (includeNoChange) { - values.unshift({ - key: 'noChange', - value: 'No Change', - disabled: true - }); - } - - if (includeMixed) { - values.unshift({ - key: 'mixed', - value: '(Mixed)', - disabled: true - }); - } - - return { - values - }; - } - ); -} - -class LanguageProfileSelectInputConnector extends Component { - - // - // Lifecycle - - componentDidMount() { - const { - name, - value, - values - } = this.props; - - if (!value || !_.some(values, (option) => parseInt(option.key) === value)) { - const firstValue = _.find(values, (option) => !isNaN(parseInt(option.key))); - - if (firstValue) { - this.onChange({ name, value: firstValue.key }); - } - } - } - - // - // Listeners - - onChange = ({ name, value }) => { - this.props.onChange({ name, value: parseInt(value) }); - }; - - // - // Render - - render() { - return ( - - ); - } -} - -LanguageProfileSelectInputConnector.propTypes = { - name: PropTypes.string.isRequired, - value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - values: PropTypes.arrayOf(PropTypes.object).isRequired, - includeNoChange: PropTypes.bool.isRequired, - onChange: PropTypes.func.isRequired -}; - -LanguageProfileSelectInputConnector.defaultProps = { - includeNoChange: false -}; - -export default connect(createMapStateToProps)(LanguageProfileSelectInputConnector); diff --git a/frontend/src/Components/Form/LanguageSelectInputConnector.js b/frontend/src/Components/Form/LanguageSelectInputConnector.js new file mode 100644 index 000000000..dd3a52017 --- /dev/null +++ b/frontend/src/Components/Form/LanguageSelectInputConnector.js @@ -0,0 +1,52 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import EnhancedSelectInput from './EnhancedSelectInput'; + +function createMapStateToProps() { + return createSelector( + (state, { values }) => values, + ( languages ) => { + + const minId = languages.reduce((min, v) => (v.key < 1 ? v.key : min), languages[0].key); + + const values = languages.map(({ key, value }) => { + return { + key, + value, + dividerAfter: minId < 1 ? key === minId : false + }; + }); + + return { + values + }; + } + ); +} + +class LanguageSelectInputConnector extends Component { + + // + // Render + + render() { + + return ( + + ); + } +} + +LanguageSelectInputConnector.propTypes = { + name: PropTypes.string.isRequired, + value: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.number]).isRequired, + values: PropTypes.arrayOf(PropTypes.object).isRequired, + onChange: PropTypes.func.isRequired +}; + +export default connect(createMapStateToProps)(LanguageSelectInputConnector); diff --git a/frontend/src/Components/Page/ErrorPage.js b/frontend/src/Components/Page/ErrorPage.js index 7e2f5e405..fbbc1af25 100644 --- a/frontend/src/Components/Page/ErrorPage.js +++ b/frontend/src/Components/Page/ErrorPage.js @@ -11,7 +11,6 @@ function ErrorPage(props) { customFiltersError, tagsError, qualityProfilesError, - languageProfilesError, uiSettingsError, systemStatusError } = props; @@ -28,8 +27,6 @@ function ErrorPage(props) { errorMessage = getErrorMessage(tagsError, 'Failed to load tags from API'); } else if (qualityProfilesError) { errorMessage = getErrorMessage(qualityProfilesError, 'Failed to load quality profiles from API'); - } else if (languageProfilesError) { - errorMessage = getErrorMessage(languageProfilesError, 'Failed to load language profiles from API'); } else if (uiSettingsError) { errorMessage = getErrorMessage(uiSettingsError, 'Failed to load UI settings from API'); } else if (systemStatusError) { @@ -56,7 +53,6 @@ ErrorPage.propTypes = { customFiltersError: PropTypes.object, tagsError: PropTypes.object, qualityProfilesError: PropTypes.object, - languageProfilesError: PropTypes.object, uiSettingsError: PropTypes.object, systemStatusError: PropTypes.object }; diff --git a/frontend/src/Components/Page/PageConnector.js b/frontend/src/Components/Page/PageConnector.js index 0cd28240b..a3127eddf 100644 --- a/frontend/src/Components/Page/PageConnector.js +++ b/frontend/src/Components/Page/PageConnector.js @@ -6,7 +6,7 @@ import { createSelector } from 'reselect'; import { saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions'; import { fetchCustomFilters } from 'Store/Actions/customFilterActions'; import { fetchSeries } from 'Store/Actions/seriesActions'; -import { fetchImportLists, fetchLanguageProfiles, fetchQualityProfiles, fetchUISettings } from 'Store/Actions/settingsActions'; +import { fetchImportLists, fetchLanguages, fetchQualityProfiles, fetchUISettings } from 'Store/Actions/settingsActions'; import { fetchStatus } from 'Store/Actions/systemActions'; import { fetchTags } from 'Store/Actions/tagActions'; import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; @@ -49,7 +49,7 @@ const selectIsPopulated = createSelector( (state) => state.tags.isPopulated, (state) => state.settings.ui.isPopulated, (state) => state.settings.qualityProfiles.isPopulated, - (state) => state.settings.languageProfiles.isPopulated, + (state) => state.settings.languages.isPopulated, (state) => state.settings.importLists.isPopulated, (state) => state.system.status.isPopulated, ( @@ -58,7 +58,7 @@ const selectIsPopulated = createSelector( tagsIsPopulated, uiSettingsIsPopulated, qualityProfilesIsPopulated, - languageProfilesIsPopulated, + languagesIsPopulated, importListsIsPopulated, systemStatusIsPopulated ) => { @@ -68,7 +68,7 @@ const selectIsPopulated = createSelector( tagsIsPopulated && uiSettingsIsPopulated && qualityProfilesIsPopulated && - languageProfilesIsPopulated && + languagesIsPopulated && importListsIsPopulated && systemStatusIsPopulated ); @@ -81,7 +81,7 @@ const selectErrors = createSelector( (state) => state.tags.error, (state) => state.settings.ui.error, (state) => state.settings.qualityProfiles.error, - (state) => state.settings.languageProfiles.error, + (state) => state.settings.languages.error, (state) => state.settings.importLists.error, (state) => state.system.status.error, ( @@ -90,7 +90,7 @@ const selectErrors = createSelector( tagsError, uiSettingsError, qualityProfilesError, - languageProfilesError, + languagesError, importListsError, systemStatusError ) => { @@ -100,7 +100,7 @@ const selectErrors = createSelector( tagsError || uiSettingsError || qualityProfilesError || - languageProfilesError || + languagesError || importListsError || systemStatusError ); @@ -112,7 +112,7 @@ const selectErrors = createSelector( tagsError, uiSettingsError, qualityProfilesError, - languageProfilesError, + languagesError, importListsError, systemStatusError }; @@ -161,8 +161,8 @@ function createMapDispatchToProps(dispatch, props) { dispatchFetchQualityProfiles() { dispatch(fetchQualityProfiles()); }, - dispatchFetchLanguageProfiles() { - dispatch(fetchLanguageProfiles()); + dispatchFetchLanguages() { + dispatch(fetchLanguages()); }, dispatchFetchImportLists() { dispatch(fetchImportLists()); @@ -201,7 +201,7 @@ class PageConnector extends Component { this.props.dispatchFetchCustomFilters(); this.props.dispatchFetchTags(); this.props.dispatchFetchQualityProfiles(); - this.props.dispatchFetchLanguageProfiles(); + this.props.dispatchFetchLanguages(); this.props.dispatchFetchImportLists(); this.props.dispatchFetchUISettings(); this.props.dispatchFetchStatus(); @@ -225,7 +225,7 @@ class PageConnector extends Component { dispatchFetchSeries, dispatchFetchTags, dispatchFetchQualityProfiles, - dispatchFetchLanguageProfiles, + dispatchFetchLanguages, dispatchFetchImportLists, dispatchFetchUISettings, dispatchFetchStatus, @@ -264,7 +264,7 @@ PageConnector.propTypes = { dispatchFetchCustomFilters: PropTypes.func.isRequired, dispatchFetchTags: PropTypes.func.isRequired, dispatchFetchQualityProfiles: PropTypes.func.isRequired, - dispatchFetchLanguageProfiles: PropTypes.func.isRequired, + dispatchFetchLanguages: PropTypes.func.isRequired, dispatchFetchImportLists: PropTypes.func.isRequired, dispatchFetchUISettings: PropTypes.func.isRequired, dispatchFetchStatus: PropTypes.func.isRequired, diff --git a/frontend/src/Episode/EpisodeLanguage.js b/frontend/src/Episode/EpisodeLanguage.js deleted file mode 100644 index 52c8b3390..000000000 --- a/frontend/src/Episode/EpisodeLanguage.js +++ /dev/null @@ -1,37 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import Label from 'Components/Label'; -import { kinds } from 'Helpers/Props'; - -function EpisodeLanguage(props) { - const { - className, - language, - isCutoffNotMet - } = props; - - if (!language) { - return null; - } - - return ( - - ); -} - -EpisodeLanguage.propTypes = { - className: PropTypes.string, - language: PropTypes.object, - isCutoffNotMet: PropTypes.bool -}; - -EpisodeLanguage.defaultProps = { - isCutoffNotMet: true -}; - -export default EpisodeLanguage; diff --git a/frontend/src/Episode/EpisodeLanguages.js b/frontend/src/Episode/EpisodeLanguages.js new file mode 100644 index 000000000..53e3854cc --- /dev/null +++ b/frontend/src/Episode/EpisodeLanguages.js @@ -0,0 +1,69 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import Label from 'Components/Label'; +import Popover from 'Components/Tooltip/Popover'; +import { kinds, tooltipPositions } from 'Helpers/Props'; + +function EpisodeLanguages(props) { + const { + className, + languages, + isCutoffNotMet + } = props; + + if (!languages) { + return null; + } + + if (languages.length === 1) { + return ( + + ); + } + + return ( + + Multi-Languages + + } + title={'Languages'} + body={ +
    + { + languages.map((language) => { + return ( +
  • + {language.name} +
  • + ); + }) + } +
+ } + position={tooltipPositions.LEFT} + /> + ); +} + +EpisodeLanguages.propTypes = { + className: PropTypes.string, + languages: PropTypes.arrayOf(PropTypes.object), + isCutoffNotMet: PropTypes.bool +}; + +EpisodeLanguages.defaultProps = { + isCutoffNotMet: true +}; + +export default EpisodeLanguages; diff --git a/frontend/src/Episode/History/EpisodeHistory.js b/frontend/src/Episode/History/EpisodeHistory.js index e85c9589d..ebf6eaaff 100644 --- a/frontend/src/Episode/History/EpisodeHistory.js +++ b/frontend/src/Episode/History/EpisodeHistory.js @@ -18,8 +18,8 @@ const columns = [ isVisible: true }, { - name: 'language', - label: 'Language', + name: 'languages', + label: 'Languages', isVisible: true }, { diff --git a/frontend/src/Episode/History/EpisodeHistoryRow.js b/frontend/src/Episode/History/EpisodeHistoryRow.js index f6a824b28..e02883124 100644 --- a/frontend/src/Episode/History/EpisodeHistoryRow.js +++ b/frontend/src/Episode/History/EpisodeHistoryRow.js @@ -11,7 +11,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRow from 'Components/Table/TableRow'; import Popover from 'Components/Tooltip/Popover'; import Tooltip from 'Components/Tooltip/Tooltip'; -import EpisodeLanguage from 'Episode/EpisodeLanguage'; +import EpisodeLanguages from 'Episode/EpisodeLanguages'; import EpisodeQuality from 'Episode/EpisodeQuality'; import { icons, kinds, tooltipPositions } from 'Helpers/Props'; import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore'; @@ -65,7 +65,7 @@ class EpisodeHistoryRow extends Component { const { eventType, sourceTitle, - language, + languages, languageCutoffNotMet, quality, qualityCutoffNotMet, @@ -90,8 +90,8 @@ class EpisodeHistoryRow extends Component { - @@ -177,7 +177,7 @@ EpisodeHistoryRow.propTypes = { id: PropTypes.number.isRequired, eventType: PropTypes.string.isRequired, sourceTitle: PropTypes.string.isRequired, - language: PropTypes.object.isRequired, + languages: PropTypes.arrayOf(PropTypes.object).isRequired, languageCutoffNotMet: PropTypes.bool.isRequired, quality: PropTypes.object.isRequired, qualityCutoffNotMet: PropTypes.bool.isRequired, diff --git a/frontend/src/Episode/Summary/EpisodeFileRow.css b/frontend/src/Episode/Summary/EpisodeFileRow.css index 565a785f0..a8b5f9372 100644 --- a/frontend/src/Episode/Summary/EpisodeFileRow.css +++ b/frontend/src/Episode/Summary/EpisodeFileRow.css @@ -4,7 +4,7 @@ width: 100px; } -.language, +.languages, .quality { composes: cell from '~Components/Table/Cells/TableRowCell.css'; diff --git a/frontend/src/Episode/Summary/EpisodeFileRow.js b/frontend/src/Episode/Summary/EpisodeFileRow.js index 32994a197..901771a48 100644 --- a/frontend/src/Episode/Summary/EpisodeFileRow.js +++ b/frontend/src/Episode/Summary/EpisodeFileRow.js @@ -6,7 +6,7 @@ import ConfirmModal from 'Components/Modal/ConfirmModal'; import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRow from 'Components/Table/TableRow'; import Popover from 'Components/Tooltip/Popover'; -import EpisodeLanguage from 'Episode/EpisodeLanguage'; +import EpisodeLanguages from 'Episode/EpisodeLanguages'; import EpisodeQuality from 'Episode/EpisodeQuality'; import { icons, kinds, tooltipPositions } from 'Helpers/Props'; import formatBytes from 'Utilities/Number/formatBytes'; @@ -50,7 +50,7 @@ class EpisodeFileRow extends Component { const { path, size, - language, + languages, quality, languageCutoffNotMet, qualityCutoffNotMet, @@ -87,14 +87,14 @@ class EpisodeFileRow extends Component { ); } - if (name === 'language') { + if (name === 'languages') { return ( - @@ -167,7 +167,7 @@ class EpisodeFileRow extends Component { EpisodeFileRow.propTypes = { path: PropTypes.string.isRequired, size: PropTypes.number.isRequired, - language: PropTypes.object.isRequired, + languages: PropTypes.arrayOf(PropTypes.object).isRequired, languageCutoffNotMet: PropTypes.bool.isRequired, quality: PropTypes.object.isRequired, qualityCutoffNotMet: PropTypes.bool.isRequired, diff --git a/frontend/src/Episode/Summary/EpisodeSummary.js b/frontend/src/Episode/Summary/EpisodeSummary.js index 37d9f2150..74e6d7336 100644 --- a/frontend/src/Episode/Summary/EpisodeSummary.js +++ b/frontend/src/Episode/Summary/EpisodeSummary.js @@ -24,8 +24,8 @@ const columns = [ isVisible: true }, { - name: 'language', - label: 'Language', + name: 'languages', + label: 'Languages', isSortable: false, isVisible: true }, @@ -84,7 +84,7 @@ class EpisodeSummary extends Component { mediaInfo, path, size, - language, + languages, quality, languageCutoffNotMet, qualityCutoffNotMet, @@ -132,7 +132,7 @@ class EpisodeSummary extends Component { { return { - language: episodeFile ? episodeFile.language : undefined + languages: episodeFile ? episodeFile.languages : undefined }; } ); } -export default connect(createMapStateToProps)(EpisodeLanguage); +export default connect(createMapStateToProps)(EpisodeLanguages); diff --git a/frontend/src/Helpers/Props/filterBuilderValueTypes.js b/frontend/src/Helpers/Props/filterBuilderValueTypes.js index 0490975a6..a1f8f499d 100644 --- a/frontend/src/Helpers/Props/filterBuilderValueTypes.js +++ b/frontend/src/Helpers/Props/filterBuilderValueTypes.js @@ -3,7 +3,6 @@ export const BYTES = 'bytes'; export const DATE = 'date'; export const DEFAULT = 'default'; export const INDEXER = 'indexer'; -export const LANGUAGE_PROFILE = 'languageProfile'; export const PROTOCOL = 'protocol'; export const QUALITY = 'quality'; export const QUALITY_PROFILE = 'qualityProfile'; diff --git a/frontend/src/Helpers/Props/inputTypes.js b/frontend/src/Helpers/Props/inputTypes.js index 8bba563a2..d0aecd281 100644 --- a/frontend/src/Helpers/Props/inputTypes.js +++ b/frontend/src/Helpers/Props/inputTypes.js @@ -9,8 +9,8 @@ export const OAUTH = 'oauth'; export const PASSWORD = 'password'; export const PATH = 'path'; export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect'; -export const LANGUAGE_PROFILE_SELECT = 'languageProfileSelect'; export const INDEXER_SELECT = 'indexerSelect'; +export const LANGUAGE_SELECT = 'languageSelect'; export const DOWNLOAD_CLIENT_SELECT = 'downloadClientSelect'; export const ROOT_FOLDER_SELECT = 'rootFolderSelect'; export const SELECT = 'select'; @@ -35,10 +35,10 @@ export const all = [ PASSWORD, PATH, QUALITY_PROFILE_SELECT, - LANGUAGE_PROFILE_SELECT, INDEXER_SELECT, DOWNLOAD_CLIENT_SELECT, ROOT_FOLDER_SELECT, + LANGUAGE_SELECT, SELECT, DYNAMIC_SELECT, SERIES_TYPE_SELECT, diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js index 214d8c189..4d52272c7 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js @@ -66,8 +66,8 @@ const columns = [ isVisible: true }, { - name: 'language', - label: 'Language', + name: 'languages', + label: 'Languages', isSortable: true, isVisible: true }, diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContentConnector.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContentConnector.js index 92db91013..360f59652 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContentConnector.js +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContentConnector.js @@ -193,7 +193,7 @@ class InteractiveImportModalContentConnector extends Component { episodes, releaseGroup, quality, - language, + languages, episodeFileId } = item; @@ -217,8 +217,8 @@ class InteractiveImportModalContentConnector extends Component { return; } - if (!language) { - this.setState({ interactiveImportErrorMessage: 'Language must be chosen for each selected file' }); + if (!languages) { + this.setState({ interactiveImportErrorMessage: 'Language(s) must be chosen for each selected file' }); return; } @@ -230,7 +230,7 @@ class InteractiveImportModalContentConnector extends Component { id: episodeFileId, releaseGroup, quality, - language + languages }); return; @@ -244,7 +244,7 @@ class InteractiveImportModalContentConnector extends Component { episodeIds: episodes.map((e) => e.id), releaseGroup, quality, - language, + languages, downloadId: this.props.downloadId, episodeFileId }); diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.css b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.css index 012108f8c..b2304553f 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.css +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.css @@ -5,7 +5,7 @@ } .quality, -.language { +.languages { composes: cell from '~Components/Table/Cells/TableRowCell.css'; text-align: center; diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js index ba8c29ae5..8e89c4b9e 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js @@ -7,7 +7,7 @@ import TableRowCellButton from 'Components/Table/Cells/TableRowCellButton'; import TableSelectCell from 'Components/Table/Cells/TableSelectCell'; import TableRow from 'Components/Table/TableRow'; import Popover from 'Components/Tooltip/Popover'; -import EpisodeLanguage from 'Episode/EpisodeLanguage'; +import EpisodeLanguages from 'Episode/EpisodeLanguages'; import EpisodeQuality from 'Episode/EpisodeQuality'; import { icons, kinds, tooltipPositions } from 'Helpers/Props'; import SelectEpisodeModal from 'InteractiveImport/Episode/SelectEpisodeModal'; @@ -47,7 +47,7 @@ class InteractiveImportRow extends Component { seasonNumber, episodes, quality, - language, + languages, episodeFileId, columns } = this.props; @@ -58,7 +58,7 @@ class InteractiveImportRow extends Component { seasonNumber != null && episodes.length && quality && - language + languages ) { this.props.onSelectedChange({ id, @@ -79,7 +79,7 @@ class InteractiveImportRow extends Component { seasonNumber, episodes, quality, - language, + languages, isSelected, onValidRowChange } = this.props; @@ -89,7 +89,7 @@ class InteractiveImportRow extends Component { prevProps.seasonNumber === seasonNumber && !hasDifferentItems(prevProps.episodes, episodes) && prevProps.quality === quality && - prevProps.language === language && + prevProps.languages === languages && prevProps.isSelected === isSelected ) { return; @@ -100,7 +100,7 @@ class InteractiveImportRow extends Component { seasonNumber != null && episodes.length && quality && - language + languages ); if (isSelected && !isValid) { @@ -210,7 +210,7 @@ class InteractiveImportRow extends Component { seasonNumber, episodes, quality, - language, + languages, releaseGroup, size, rejections, @@ -252,7 +252,7 @@ class InteractiveImportRow extends Component { const showEpisodeNumbersPlaceholder = isSelected && Number.isInteger(seasonNumber) && !episodes.length; const showReleaseGroupPlaceholder = isSelected && !releaseGroup; const showQualityPlaceholder = isSelected && !quality; - const showLanguagePlaceholder = isSelected && !language; + const showLanguagePlaceholder = isSelected && !languages; return ( @@ -352,10 +352,10 @@ class InteractiveImportRow extends Component { } { - !showLanguagePlaceholder && !!language && - } @@ -441,7 +441,7 @@ class InteractiveImportRow extends Component { @@ -460,7 +460,7 @@ InteractiveImportRow.propTypes = { episodes: PropTypes.arrayOf(PropTypes.object).isRequired, releaseGroup: PropTypes.string, quality: PropTypes.object, - language: PropTypes.object, + languages: PropTypes.arrayOf(PropTypes.object), size: PropTypes.number.isRequired, rejections: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired, diff --git a/frontend/src/InteractiveImport/Language/SelectLanguageModalContentConnector.js b/frontend/src/InteractiveImport/Language/SelectLanguageModalContentConnector.js index bda7193d0..5ed9660dc 100644 --- a/frontend/src/InteractiveImport/Language/SelectLanguageModalContentConnector.js +++ b/frontend/src/InteractiveImport/Language/SelectLanguageModalContentConnector.js @@ -4,7 +4,6 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import { reprocessInteractiveImportItems, updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions'; -import { fetchLanguageProfileSchema } from 'Store/Actions/settingsActions'; import createLanguagesSelector from 'Store/Selectors/createLanguagesSelector'; import SelectLanguageModalContent from './SelectLanguageModalContent'; @@ -18,22 +17,12 @@ function createMapStateToProps() { } const mapDispatchToProps = { - dispatchFetchLanguageProfileSchema: fetchLanguageProfileSchema, dispatchUpdateInteractiveImportItems: updateInteractiveImportItems, dispatchReprocessInteractiveImportItems: reprocessInteractiveImportItems }; class SelectLanguageModalContentConnector extends Component { - // - // Lifecycle - - componentDidMount = () => { - if (!this.props.isPopulated) { - this.props.dispatchFetchLanguageProfileSchema(); - } - }; - // // Listeners @@ -44,13 +33,20 @@ class SelectLanguageModalContentConnector extends Component { dispatchReprocessInteractiveImportItems } = this.props; - const languageId = parseInt(value); - const language = _.find(this.props.items, - (item) => item.language.id === languageId).language; + const languages = []; + + value.forEach((languageId) => { + const language = _.find(this.props.items, + (item) => item.id === parseInt(languageId)); + + if (language !== undefined) { + languages.push(language); + } + }); dispatchUpdateInteractiveImportItems({ ids, - language + languages }); dispatchReprocessInteractiveImportItems({ ids }); @@ -77,7 +73,6 @@ SelectLanguageModalContentConnector.propTypes = { isPopulated: PropTypes.bool.isRequired, error: PropTypes.object, items: PropTypes.arrayOf(PropTypes.object).isRequired, - dispatchFetchLanguageProfileSchema: PropTypes.func.isRequired, dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired, dispatchReprocessInteractiveImportItems: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired diff --git a/frontend/src/InteractiveSearch/InteractiveSearch.js b/frontend/src/InteractiveSearch/InteractiveSearch.js index da0921314..c3eadb2ae 100644 --- a/frontend/src/InteractiveSearch/InteractiveSearch.js +++ b/frontend/src/InteractiveSearch/InteractiveSearch.js @@ -51,7 +51,7 @@ const columns = [ }, { name: 'languageWeight', - label: 'Language', + label: 'Languages', isSortable: true, isVisible: true }, diff --git a/frontend/src/InteractiveSearch/InteractiveSearchRow.css b/frontend/src/InteractiveSearch/InteractiveSearchRow.css index 4f7037ca6..b51f12375 100644 --- a/frontend/src/InteractiveSearch/InteractiveSearchRow.css +++ b/frontend/src/InteractiveSearch/InteractiveSearchRow.css @@ -24,13 +24,13 @@ } .quality, -.language { +.languages { composes: cell from '~Components/Table/Cells/TableRowCell.css'; text-align: center; } -.language { +.languages { width: 100px; } diff --git a/frontend/src/InteractiveSearch/InteractiveSearchRow.js b/frontend/src/InteractiveSearch/InteractiveSearchRow.js index b0a7bed2b..dd68d00fc 100644 --- a/frontend/src/InteractiveSearch/InteractiveSearchRow.js +++ b/frontend/src/InteractiveSearch/InteractiveSearchRow.js @@ -10,7 +10,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRow from 'Components/Table/TableRow'; import Popover from 'Components/Tooltip/Popover'; import Tooltip from 'Components/Tooltip/Tooltip'; -import EpisodeLanguage from 'Episode/EpisodeLanguage'; +import EpisodeLanguages from 'Episode/EpisodeLanguages'; import EpisodeQuality from 'Episode/EpisodeQuality'; import { icons, kinds, tooltipPositions } from 'Helpers/Props'; import formatDateTime from 'Utilities/Date/formatDateTime'; @@ -116,7 +116,7 @@ class InteractiveSearchRow extends Component { seeders, leechers, quality, - language, + languages, customFormatScore, customFormats, sceneMapping, @@ -188,8 +188,8 @@ class InteractiveSearchRow extends Component { } - - + + @@ -286,7 +286,7 @@ InteractiveSearchRow.propTypes = { seeders: PropTypes.number, leechers: PropTypes.number, quality: PropTypes.object.isRequired, - language: PropTypes.object.isRequired, + languages: PropTypes.arrayOf(PropTypes.object).isRequired, customFormats: PropTypes.arrayOf(PropTypes.object), customFormatScore: PropTypes.number.isRequired, sceneMapping: PropTypes.object, diff --git a/frontend/src/Series/Details/EpisodeRow.css b/frontend/src/Series/Details/EpisodeRow.css index 2585248a0..c9fa30526 100644 --- a/frontend/src/Series/Details/EpisodeRow.css +++ b/frontend/src/Series/Details/EpisodeRow.css @@ -28,7 +28,7 @@ width: 100px; } -.language, +.languages, .audio, .video, .status { diff --git a/frontend/src/Series/Details/EpisodeRow.js b/frontend/src/Series/Details/EpisodeRow.js index 0806f7f59..10e4daffb 100644 --- a/frontend/src/Series/Details/EpisodeRow.js +++ b/frontend/src/Series/Details/EpisodeRow.js @@ -180,11 +180,11 @@ class EpisodeRow extends Component { ); } - if (name === 'language') { + if (name === 'languages') { return ( - { - showLanguageProfile && - - Language Profile - - - - } - Series Type @@ -209,7 +193,6 @@ EditSeriesModalContent.propTypes = { title: PropTypes.string.isRequired, item: PropTypes.object.isRequired, isSaving: PropTypes.bool.isRequired, - showLanguageProfile: PropTypes.bool.isRequired, isPathChanging: PropTypes.bool.isRequired, originalPath: PropTypes.string.isRequired, onInputChange: PropTypes.func.isRequired, diff --git a/frontend/src/Series/Edit/EditSeriesModalContentConnector.js b/frontend/src/Series/Edit/EditSeriesModalContentConnector.js index 7e3fa175a..0521f92df 100644 --- a/frontend/src/Series/Edit/EditSeriesModalContentConnector.js +++ b/frontend/src/Series/Edit/EditSeriesModalContentConnector.js @@ -27,10 +27,9 @@ function createIsPathChangingSelector() { function createMapStateToProps() { return createSelector( (state) => state.series, - (state) => state.settings.languageProfiles, createSeriesSelector(), createIsPathChangingSelector(), - (seriesState, languageProfiles, series, isPathChanging) => { + (seriesState, series, isPathChanging) => { const { isSaving, saveError, @@ -41,7 +40,6 @@ function createMapStateToProps() { 'monitored', 'seasonFolder', 'qualityProfileId', - 'languageProfileId', 'seriesType', 'path', 'tags' @@ -56,7 +54,6 @@ function createMapStateToProps() { isPathChanging, originalPath: series.path, item: settings.settings, - showLanguageProfile: languageProfiles.items.length > 1, ...settings }; } diff --git a/frontend/src/Series/Editor/SeriesEditor.js b/frontend/src/Series/Editor/SeriesEditor.js index 1439f2c65..b3aaffe12 100644 --- a/frontend/src/Series/Editor/SeriesEditor.js +++ b/frontend/src/Series/Editor/SeriesEditor.js @@ -212,7 +212,6 @@ class SeriesEditor extends Component { deleteError={deleteError} isOrganizingSeries={isOrganizingSeries} columns={columns} - showLanguageProfile={columns.find((column) => column.name === 'languageProfileId').isVisible} onSaveSelected={this.onSaveSelected} onOrganizeSeriesPress={this.onOrganizeSeriesPress} /> diff --git a/frontend/src/Series/Editor/SeriesEditorFooter.js b/frontend/src/Series/Editor/SeriesEditorFooter.js index f31b7f046..f427b42bd 100644 --- a/frontend/src/Series/Editor/SeriesEditorFooter.js +++ b/frontend/src/Series/Editor/SeriesEditorFooter.js @@ -1,6 +1,5 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import LanguageProfileSelectInputConnector from 'Components/Form/LanguageProfileSelectInputConnector'; import QualityProfileSelectInputConnector from 'Components/Form/QualityProfileSelectInputConnector'; import RootFolderSelectInputConnector from 'Components/Form/RootFolderSelectInputConnector'; import SelectInput from 'Components/Form/SelectInput'; @@ -27,7 +26,6 @@ class SeriesEditorFooter extends Component { this.state = { monitored: NO_CHANGE, qualityProfileId: NO_CHANGE, - languageProfileId: NO_CHANGE, seriesType: NO_CHANGE, seasonFolder: NO_CHANGE, rootFolderPath: NO_CHANGE, @@ -49,7 +47,6 @@ class SeriesEditorFooter extends Component { this.setState({ monitored: NO_CHANGE, qualityProfileId: NO_CHANGE, - languageProfileId: NO_CHANGE, seriesType: NO_CHANGE, seasonFolder: NO_CHANGE, rootFolderPath: NO_CHANGE, @@ -152,7 +149,6 @@ class SeriesEditorFooter extends Component { const { monitored, qualityProfileId, - languageProfileId, seriesType, seasonFolder, rootFolderPath, @@ -225,28 +221,6 @@ class SeriesEditorFooter extends Component { ); } - if (name === 'languageProfileId') { - return ( -
- - - -
- ); - } - if (name === 'seriesType') { return (
- {languageProfile.name} - - ); - } - if (name === 'seriesType') { return ( @@ -167,7 +158,6 @@ SeriesEditorRow.propTypes = { titleSlug: PropTypes.string.isRequired, title: PropTypes.string.isRequired, monitored: PropTypes.bool.isRequired, - languageProfile: PropTypes.object.isRequired, qualityProfile: PropTypes.object.isRequired, seriesType: PropTypes.string.isRequired, seasonFolder: PropTypes.bool.isRequired, diff --git a/frontend/src/Series/Editor/SeriesEditorRowConnector.js b/frontend/src/Series/Editor/SeriesEditorRowConnector.js index 3d1ee2e71..1b70e92fe 100644 --- a/frontend/src/Series/Editor/SeriesEditorRowConnector.js +++ b/frontend/src/Series/Editor/SeriesEditorRowConnector.js @@ -2,17 +2,14 @@ import PropTypes from 'prop-types'; import React from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import createLanguageProfileSelector from 'Store/Selectors/createLanguageProfileSelector'; import createQualityProfileSelector from 'Store/Selectors/createQualityProfileSelector'; import SeriesEditorRow from './SeriesEditorRow'; function createMapStateToProps() { return createSelector( - createLanguageProfileSelector(), createQualityProfileSelector(), - (languageProfile, qualityProfile) => { + (qualityProfile) => { return { - languageProfile, qualityProfile }; } diff --git a/frontend/src/Series/History/SeriesHistoryModalContent.js b/frontend/src/Series/History/SeriesHistoryModalContent.js index dc6d07a81..3bc19b857 100644 --- a/frontend/src/Series/History/SeriesHistoryModalContent.js +++ b/frontend/src/Series/History/SeriesHistoryModalContent.js @@ -29,8 +29,8 @@ const columns = [ isVisible: true }, { - name: 'language', - label: 'Language', + name: 'languages', + label: 'Languages', isVisible: true }, { diff --git a/frontend/src/Series/History/SeriesHistoryRow.js b/frontend/src/Series/History/SeriesHistoryRow.js index a45495a9a..98a67cc93 100644 --- a/frontend/src/Series/History/SeriesHistoryRow.js +++ b/frontend/src/Series/History/SeriesHistoryRow.js @@ -11,7 +11,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRow from 'Components/Table/TableRow'; import Popover from 'Components/Tooltip/Popover'; import Tooltip from 'Components/Tooltip/Tooltip'; -import EpisodeLanguage from 'Episode/EpisodeLanguage'; +import EpisodeLanguages from 'Episode/EpisodeLanguages'; import EpisodeNumber from 'Episode/EpisodeNumber'; import EpisodeQuality from 'Episode/EpisodeQuality'; import SeasonEpisodeNumber from 'Episode/SeasonEpisodeNumber'; @@ -67,7 +67,7 @@ class SeriesHistoryRow extends Component { const { eventType, sourceTitle, - language, + languages, languageCutoffNotMet, quality, qualityCutoffNotMet, @@ -110,8 +110,8 @@ class SeriesHistoryRow extends Component { - @@ -197,7 +197,7 @@ SeriesHistoryRow.propTypes = { id: PropTypes.number.isRequired, eventType: PropTypes.string.isRequired, sourceTitle: PropTypes.string.isRequired, - language: PropTypes.object.isRequired, + languages: PropTypes.object.isRequired, languageCutoffNotMet: PropTypes.bool.isRequired, quality: PropTypes.object.isRequired, qualityCutoffNotMet: PropTypes.bool.isRequired, diff --git a/frontend/src/Series/Index/Menus/SeriesIndexSortMenu.js b/frontend/src/Series/Index/Menus/SeriesIndexSortMenu.js index 4ca1d86e6..018a4c8eb 100644 --- a/frontend/src/Series/Index/Menus/SeriesIndexSortMenu.js +++ b/frontend/src/Series/Index/Menus/SeriesIndexSortMenu.js @@ -55,15 +55,6 @@ function SeriesIndexSortMenu(props) { Quality Profile - - Language Profile - -
diff --git a/frontend/src/Series/Index/Posters/SeriesIndexPosters.js b/frontend/src/Series/Index/Posters/SeriesIndexPosters.js index 4339e0d2c..5e856bfe9 100644 --- a/frontend/src/Series/Index/Posters/SeriesIndexPosters.js +++ b/frontend/src/Series/Index/Posters/SeriesIndexPosters.js @@ -243,7 +243,6 @@ class SeriesIndexPosters extends Component { timeFormat={timeFormat} style={style} seriesId={series.id} - languageProfileId={series.languageProfileId} qualityProfileId={series.qualityProfileId} />
diff --git a/frontend/src/Series/Index/SeriesIndexItemConnector.js b/frontend/src/Series/Index/SeriesIndexItemConnector.js index 7f5380af2..bff4a77b1 100644 --- a/frontend/src/Series/Index/SeriesIndexItemConnector.js +++ b/frontend/src/Series/Index/SeriesIndexItemConnector.js @@ -6,7 +6,6 @@ import { createSelector } from 'reselect'; import * as commandNames from 'Commands/commandNames'; import { executeCommand } from 'Store/Actions/commandActions'; import createExecutingCommandsSelector from 'Store/Selectors/createExecutingCommandsSelector'; -import createSeriesLanguageProfileSelector from 'Store/Selectors/createSeriesLanguageProfileSelector'; import createSeriesQualityProfileSelector from 'Store/Selectors/createSeriesQualityProfileSelector'; import createSeriesSelector from 'Store/Selectors/createSeriesSelector'; @@ -32,13 +31,11 @@ function createMapStateToProps() { return createSelector( createSeriesSelector(), createSeriesQualityProfileSelector(), - createSeriesLanguageProfileSelector(), selectShowSearchAction(), createExecutingCommandsSelector(), ( series, qualityProfile, - languageProfile, showSearchAction, executingCommands ) => { @@ -71,7 +68,6 @@ function createMapStateToProps() { return { ...series, qualityProfile, - languageProfile, latestSeason, showSearchAction, isRefreshingSeries, diff --git a/frontend/src/Series/Index/Table/SeriesIndexHeader.css b/frontend/src/Series/Index/Table/SeriesIndexHeader.css index ce9f0f6ed..8c5d9f7b0 100644 --- a/frontend/src/Series/Index/Table/SeriesIndexHeader.css +++ b/frontend/src/Series/Index/Table/SeriesIndexHeader.css @@ -30,8 +30,7 @@ flex: 2 0 90px; } -.qualityProfileId, -.languageProfileId { +.qualityProfileId { composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css'; flex: 1 0 125px; diff --git a/frontend/src/Series/Index/Table/SeriesIndexRow.css b/frontend/src/Series/Index/Table/SeriesIndexRow.css index c5375a7d7..85d313c05 100644 --- a/frontend/src/Series/Index/Table/SeriesIndexRow.css +++ b/frontend/src/Series/Index/Table/SeriesIndexRow.css @@ -66,8 +66,7 @@ flex: 2 0 90px; } -.qualityProfileId, -.languageProfileId { +.qualityProfileId { composes: cell; flex: 1 0 125px; diff --git a/frontend/src/Series/Index/Table/SeriesIndexRow.js b/frontend/src/Series/Index/Table/SeriesIndexRow.js index c33ff6783..96052addc 100644 --- a/frontend/src/Series/Index/Table/SeriesIndexRow.js +++ b/frontend/src/Series/Index/Table/SeriesIndexRow.js @@ -86,7 +86,6 @@ class SeriesIndexRow extends Component { seriesType, network, qualityProfile, - languageProfile, nextAiring, previousAiring, added, @@ -224,17 +223,6 @@ class SeriesIndexRow extends Component { ); } - if (name === 'languageProfileId') { - return ( - - {languageProfile.name} - - ); - } - if (name === 'nextAiring') { return ( diff --git a/frontend/src/Series/Index/Table/hasGrowableColumns.js b/frontend/src/Series/Index/Table/hasGrowableColumns.js index f51824314..7494e2402 100644 --- a/frontend/src/Series/Index/Table/hasGrowableColumns.js +++ b/frontend/src/Series/Index/Table/hasGrowableColumns.js @@ -1,7 +1,6 @@ const growableColumns = [ 'network', 'qualityProfileId', - 'languageProfileId', 'path', 'tags' ]; diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.css b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.css index db5a98500..fd7ddf093 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.css +++ b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.css @@ -4,12 +4,6 @@ margin-right: auto; } -.hideLanguageProfile { - composes: group from '~Components/Form/FormGroup.css'; - - display: none; -} - .labelIcon { margin-left: 8px; } diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js index fb3400fd2..e71c680fc 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js +++ b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js @@ -35,7 +35,6 @@ function EditImportListModalContent(props) { onSavePress, onTestPress, onDeleteImportListPress, - showLanguageProfile, ...otherProps } = props; @@ -46,7 +45,6 @@ function EditImportListModalContent(props) { shouldMonitor, rootFolderPath, qualityProfileId, - languageProfileId, seriesType, seasonFolder, tags, @@ -148,18 +146,6 @@ function EditImportListModalContent(props) { /> - - Language Profile - - - - Series Type @@ -279,7 +265,6 @@ EditImportListModalContent.propTypes = { isTesting: PropTypes.bool.isRequired, saveError: PropTypes.object, item: PropTypes.object.isRequired, - showLanguageProfile: PropTypes.bool.isRequired, onInputChange: PropTypes.func.isRequired, onFieldChange: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired, diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContentConnector.js b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContentConnector.js index 17981a1e4..3a7ad8b1f 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContentConnector.js +++ b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContentConnector.js @@ -9,12 +9,10 @@ import EditImportListModalContent from './EditImportListModalContent'; function createMapStateToProps() { return createSelector( (state) => state.settings.advancedSettings, - (state) => state.settings.languageProfiles, createProviderSettingsSelector('importLists'), - (advancedSettings, languageProfiles, importList) => { + (advancedSettings, importList) => { return { advancedSettings, - showLanguageProfile: languageProfiles.items.length > 1, ...importList }; } diff --git a/frontend/src/Settings/Profiles/Language/EditLanguageProfileModal.js b/frontend/src/Settings/Profiles/Language/EditLanguageProfileModal.js deleted file mode 100644 index 8968f2b4b..000000000 --- a/frontend/src/Settings/Profiles/Language/EditLanguageProfileModal.js +++ /dev/null @@ -1,27 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import Modal from 'Components/Modal/Modal'; -import { sizes } from 'Helpers/Props'; -import EditLanguageProfileModalContentConnector from './EditLanguageProfileModalContentConnector'; - -function EditLanguageProfileModal({ isOpen, onModalClose, ...otherProps }) { - return ( - - - - ); -} - -EditLanguageProfileModal.propTypes = { - isOpen: PropTypes.bool.isRequired, - onModalClose: PropTypes.func.isRequired -}; - -export default EditLanguageProfileModal; diff --git a/frontend/src/Settings/Profiles/Language/EditLanguageProfileModalConnector.js b/frontend/src/Settings/Profiles/Language/EditLanguageProfileModalConnector.js deleted file mode 100644 index 5443a9062..000000000 --- a/frontend/src/Settings/Profiles/Language/EditLanguageProfileModalConnector.js +++ /dev/null @@ -1,43 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { clearPendingChanges } from 'Store/Actions/baseActions'; -import EditLanguageProfileModal from './EditLanguageProfileModal'; - -function mapStateToProps() { - return {}; -} - -const mapDispatchToProps = { - clearPendingChanges -}; - -class EditLanguageProfileModalConnector extends Component { - - // - // Listeners - - onModalClose = () => { - this.props.clearPendingChanges({ section: 'settings.languageProfiles' }); - this.props.onModalClose(); - }; - - // - // Render - - render() { - return ( - - ); - } -} - -EditLanguageProfileModalConnector.propTypes = { - onModalClose: PropTypes.func.isRequired, - clearPendingChanges: PropTypes.func.isRequired -}; - -export default connect(mapStateToProps, mapDispatchToProps)(EditLanguageProfileModalConnector); diff --git a/frontend/src/Settings/Profiles/Language/EditLanguageProfileModalContent.css b/frontend/src/Settings/Profiles/Language/EditLanguageProfileModalContent.css deleted file mode 100644 index 74dd1c8b7..000000000 --- a/frontend/src/Settings/Profiles/Language/EditLanguageProfileModalContent.css +++ /dev/null @@ -1,3 +0,0 @@ -.deleteButtonContainer { - margin-right: auto; -} diff --git a/frontend/src/Settings/Profiles/Language/EditLanguageProfileModalContent.js b/frontend/src/Settings/Profiles/Language/EditLanguageProfileModalContent.js deleted file mode 100644 index e4360beba..000000000 --- a/frontend/src/Settings/Profiles/Language/EditLanguageProfileModalContent.js +++ /dev/null @@ -1,165 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import Form from 'Components/Form/Form'; -import FormGroup from 'Components/Form/FormGroup'; -import FormInputGroup from 'Components/Form/FormInputGroup'; -import FormLabel from 'Components/Form/FormLabel'; -import Button from 'Components/Link/Button'; -import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton'; -import LoadingIndicator from 'Components/Loading/LoadingIndicator'; -import ModalBody from 'Components/Modal/ModalBody'; -import ModalContent from 'Components/Modal/ModalContent'; -import ModalFooter from 'Components/Modal/ModalFooter'; -import ModalHeader from 'Components/Modal/ModalHeader'; -import { inputTypes, kinds } from 'Helpers/Props'; -import LanguageProfileItems from './LanguageProfileItems'; -import styles from './EditLanguageProfileModalContent.css'; - -function EditLanguageProfileModalContent(props) { - const { - isFetching, - error, - isSaving, - saveError, - languages, - item, - isInUse, - onInputChange, - onCutoffChange, - onSavePress, - onModalClose, - onDeleteLanguageProfilePress, - ...otherProps - } = props; - - const { - id, - name, - upgradeAllowed, - cutoff, - languages: itemLanguages - } = item; - - return ( - - - {id ? 'Edit Language Profile' : 'Add Language Profile'} - - - - { - isFetching && - - } - - { - !isFetching && !!error && -
Unable to add a new language profile, please try again.
- } - - { - !isFetching && !error && -
- - Name - - - - - - - Upgrades Allowed - - - - - - { - upgradeAllowed.value && - - Upgrade Until - - - - } - - - - - } -
- - { - id && -
- -
- } - - - - - Save - -
-
- ); -} - -EditLanguageProfileModalContent.propTypes = { - isFetching: PropTypes.bool.isRequired, - error: PropTypes.object, - isSaving: PropTypes.bool.isRequired, - saveError: PropTypes.object, - languages: PropTypes.arrayOf(PropTypes.object).isRequired, - item: PropTypes.object.isRequired, - isInUse: PropTypes.bool.isRequired, - onInputChange: PropTypes.func.isRequired, - onCutoffChange: PropTypes.func.isRequired, - onSavePress: PropTypes.func.isRequired, - onModalClose: PropTypes.func.isRequired, - onDeleteLanguageProfilePress: PropTypes.func -}; - -export default EditLanguageProfileModalContent; diff --git a/frontend/src/Settings/Profiles/Language/EditLanguageProfileModalContentConnector.js b/frontend/src/Settings/Profiles/Language/EditLanguageProfileModalContentConnector.js deleted file mode 100644 index 911546c07..000000000 --- a/frontend/src/Settings/Profiles/Language/EditLanguageProfileModalContentConnector.js +++ /dev/null @@ -1,189 +0,0 @@ -import _ from 'lodash'; -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import { fetchLanguageProfileSchema, saveLanguageProfile, setLanguageProfileValue } from 'Store/Actions/settingsActions'; -import createProfileInUseSelector from 'Store/Selectors/createProfileInUseSelector'; -import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector'; -import EditLanguageProfileModalContent from './EditLanguageProfileModalContent'; - -function createLanguagesSelector() { - return createSelector( - createProviderSettingsSelector('languageProfiles'), - (languageProfile) => { - const languages = languageProfile.item.languages; - if (!languages || !languages.value) { - return []; - } - - return _.reduceRight(languages.value, (result, { allowed, language }) => { - if (allowed) { - result.push({ - key: language.id, - value: language.name - }); - } - - return result; - }, []); - } - ); -} - -function createMapStateToProps() { - return createSelector( - createProviderSettingsSelector('languageProfiles'), - createLanguagesSelector(), - createProfileInUseSelector('languageProfileId'), - (languageProfile, languages, isInUse) => { - return { - languages, - ...languageProfile, - isInUse - }; - } - ); -} - -const mapDispatchToProps = { - fetchLanguageProfileSchema, - setLanguageProfileValue, - saveLanguageProfile -}; - -class EditLanguageProfileModalContentConnector extends Component { - - // - // Lifecycle - - constructor(props, context) { - super(props, context); - - this.state = { - dragIndex: null, - dropIndex: null - }; - } - - componentDidMount() { - if (!this.props.id && !this.props.isPopulated) { - this.props.fetchLanguageProfileSchema(); - } - } - - componentDidUpdate(prevProps, prevState) { - if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) { - this.props.onModalClose(); - } - } - - // - // Listeners - - onInputChange = ({ name, value }) => { - this.props.setLanguageProfileValue({ name, value }); - }; - - onCutoffChange = ({ name, value }) => { - const id = parseInt(value); - const item = _.find(this.props.item.languages.value, (i) => i.language.id === id); - - this.props.setLanguageProfileValue({ name, value: item.language }); - }; - - onSavePress = () => { - this.props.saveLanguageProfile({ id: this.props.id }); - }; - - onLanguageProfileItemAllowedChange = (id, allowed) => { - const languageProfile = _.cloneDeep(this.props.item); - - const item = _.find(languageProfile.languages.value, (i) => i.language.id === id); - item.allowed = allowed; - - this.props.setLanguageProfileValue({ - name: 'languages', - value: languageProfile.languages.value - }); - - const cutoff = languageProfile.cutoff.value; - - // If the cutoff isn't allowed anymore or there isn't a cutoff set one - if (!cutoff || !_.find(languageProfile.languages.value, (i) => i.language.id === cutoff.id).allowed) { - const firstAllowed = _.find(languageProfile.languages.value, { allowed: true }); - - this.props.setLanguageProfileValue({ name: 'cutoff', value: firstAllowed ? firstAllowed.language : null }); - } - }; - - onLanguageProfileItemDragMove = (dragIndex, dropIndex) => { - if (this.state.dragIndex !== dragIndex || this.state.dropIndex !== dropIndex) { - this.setState({ - dragIndex, - dropIndex - }); - } - }; - - onLanguageProfileItemDragEnd = ({ id }, didDrop) => { - const { - dragIndex, - dropIndex - } = this.state; - - if (didDrop && dropIndex !== null) { - const languageProfile = _.cloneDeep(this.props.item); - - const languages = languageProfile.languages.value.splice(dragIndex, 1); - languageProfile.languages.value.splice(dropIndex, 0, languages[0]); - - this.props.setLanguageProfileValue({ - name: 'languages', - value: languageProfile.languages.value - }); - } - - this.setState({ - dragIndex: null, - dropIndex: null - }); - }; - - // - // Render - - render() { - if (_.isEmpty(this.props.item.languages) && !this.props.isFetching) { - return null; - } - - return ( - - ); - } -} - -EditLanguageProfileModalContentConnector.propTypes = { - id: PropTypes.number, - isFetching: PropTypes.bool.isRequired, - isPopulated: PropTypes.bool.isRequired, - isSaving: PropTypes.bool.isRequired, - saveError: PropTypes.object, - item: PropTypes.object.isRequired, - setLanguageProfileValue: PropTypes.func.isRequired, - fetchLanguageProfileSchema: PropTypes.func.isRequired, - saveLanguageProfile: PropTypes.func.isRequired, - onModalClose: PropTypes.func.isRequired -}; - -export default connect(createMapStateToProps, mapDispatchToProps)(EditLanguageProfileModalContentConnector); diff --git a/frontend/src/Settings/Profiles/Language/LanguageProfile.css b/frontend/src/Settings/Profiles/Language/LanguageProfile.css deleted file mode 100644 index da85dc483..000000000 --- a/frontend/src/Settings/Profiles/Language/LanguageProfile.css +++ /dev/null @@ -1,31 +0,0 @@ -.languageProfile { - composes: card from '~Components/Card.css'; - - width: 300px; -} - -.nameContainer { - display: flex; - justify-content: space-between; -} - -.name { - @add-mixin truncate; - - margin-bottom: 20px; - font-weight: 300; - font-size: 24px; -} - -.cloneButton { - composes: button from '~Components/Link/IconButton.css'; - - height: 36px; -} - -.languages { - display: flex; - flex-wrap: wrap; - margin-top: 5px; - pointer-events: all; -} diff --git a/frontend/src/Settings/Profiles/Language/LanguageProfile.js b/frontend/src/Settings/Profiles/Language/LanguageProfile.js deleted file mode 100644 index b353d37f4..000000000 --- a/frontend/src/Settings/Profiles/Language/LanguageProfile.js +++ /dev/null @@ -1,147 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import Card from 'Components/Card'; -import Label from 'Components/Label'; -import IconButton from 'Components/Link/IconButton'; -import ConfirmModal from 'Components/Modal/ConfirmModal'; -import { icons, kinds } from 'Helpers/Props'; -import EditLanguageProfileModalConnector from './EditLanguageProfileModalConnector'; -import styles from './LanguageProfile.css'; - -class LanguageProfile extends Component { - - // - // Lifecycle - - constructor(props, context) { - super(props, context); - - this.state = { - isEditLanguageProfileModalOpen: false, - isDeleteLanguageProfileModalOpen: false - }; - } - - // - // Listeners - - onEditLanguageProfilePress = () => { - this.setState({ isEditLanguageProfileModalOpen: true }); - }; - - onEditLanguageProfileModalClose = () => { - this.setState({ isEditLanguageProfileModalOpen: false }); - }; - - onDeleteLanguageProfilePress = () => { - this.setState({ - isEditLanguageProfileModalOpen: false, - isDeleteLanguageProfileModalOpen: true - }); - }; - - onDeleteLanguageProfileModalClose = () => { - this.setState({ isDeleteLanguageProfileModalOpen: false }); - }; - - onConfirmDeleteLanguageProfile = () => { - this.props.onConfirmDeleteLanguageProfile(this.props.id); - }; - - onCloneLanguageProfilePress = () => { - const { - id, - onCloneLanguageProfilePress - } = this.props; - - onCloneLanguageProfilePress(id); - }; - - // - // Render - - render() { - const { - id, - name, - upgradeAllowed, - cutoff, - languages, - isDeleting - } = this.props; - - return ( - -
-
- {name} -
- - -
- -
- { - languages.map((item) => { - if (!item.allowed) { - return null; - } - - const isCutoff = upgradeAllowed && item.language.id === cutoff.id; - - return ( - - ); - }) - } -
- - - - -
- ); - } -} - -LanguageProfile.propTypes = { - id: PropTypes.number.isRequired, - name: PropTypes.string.isRequired, - upgradeAllowed: PropTypes.bool.isRequired, - cutoff: PropTypes.object.isRequired, - languages: PropTypes.arrayOf(PropTypes.object).isRequired, - isDeleting: PropTypes.bool.isRequired, - onConfirmDeleteLanguageProfile: PropTypes.func.isRequired, - onCloneLanguageProfilePress: PropTypes.func.isRequired -}; - -export default LanguageProfile; diff --git a/frontend/src/Settings/Profiles/Language/LanguageProfileItem.css b/frontend/src/Settings/Profiles/Language/LanguageProfileItem.css deleted file mode 100644 index 090f5f09d..000000000 --- a/frontend/src/Settings/Profiles/Language/LanguageProfileItem.css +++ /dev/null @@ -1,44 +0,0 @@ -.languageProfileItem { - display: flex; - align-items: stretch; - width: 100%; - border: 1px solid #aaa; - border-radius: 4px; - background: var(--inputBackgroundColor); -} - -.checkContainer { - position: relative; - margin-right: 4px; - margin-bottom: 7px; - margin-left: 8px; -} - -.languageName { - display: flex; - flex-grow: 1; - margin-bottom: 0; - margin-left: 2px; - font-weight: normal; - line-height: 36px; - cursor: pointer; -} - -.dragHandle { - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - margin-left: auto; - width: $dragHandleWidth; - text-align: center; - cursor: grab; -} - -.dragIcon { - top: 0; -} - -.isDragging { - opacity: 0.25; -} diff --git a/frontend/src/Settings/Profiles/Language/LanguageProfileItem.js b/frontend/src/Settings/Profiles/Language/LanguageProfileItem.js deleted file mode 100644 index 750b81f20..000000000 --- a/frontend/src/Settings/Profiles/Language/LanguageProfileItem.js +++ /dev/null @@ -1,83 +0,0 @@ -import classNames from 'classnames'; -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import CheckInput from 'Components/Form/CheckInput'; -import Icon from 'Components/Icon'; -import { icons } from 'Helpers/Props'; -import styles from './LanguageProfileItem.css'; - -class LanguageProfileItem extends Component { - - // - // Listeners - - onAllowedChange = ({ value }) => { - const { - languageId, - onLanguageProfileItemAllowedChange - } = this.props; - - onLanguageProfileItemAllowedChange(languageId, value); - }; - - // - // Render - - render() { - const { - name, - allowed, - isDragging, - connectDragSource - } = this.props; - - return ( -
- - - { - connectDragSource( -
- -
- ) - } -
- ); - } -} - -LanguageProfileItem.propTypes = { - languageId: PropTypes.number.isRequired, - name: PropTypes.string.isRequired, - allowed: PropTypes.bool.isRequired, - sortIndex: PropTypes.number.isRequired, - isDragging: PropTypes.bool.isRequired, - connectDragSource: PropTypes.func, - onLanguageProfileItemAllowedChange: PropTypes.func -}; - -LanguageProfileItem.defaultProps = { - // The drag preview will not connect the drag handle. - connectDragSource: (node) => node -}; - -export default LanguageProfileItem; diff --git a/frontend/src/Settings/Profiles/Language/LanguageProfileItemDragPreview.css b/frontend/src/Settings/Profiles/Language/LanguageProfileItemDragPreview.css deleted file mode 100644 index b927d9bce..000000000 --- a/frontend/src/Settings/Profiles/Language/LanguageProfileItemDragPreview.css +++ /dev/null @@ -1,4 +0,0 @@ -.dragPreview { - width: 380px; - opacity: 0.75; -} diff --git a/frontend/src/Settings/Profiles/Language/LanguageProfileItemDragPreview.js b/frontend/src/Settings/Profiles/Language/LanguageProfileItemDragPreview.js deleted file mode 100644 index c18b012fe..000000000 --- a/frontend/src/Settings/Profiles/Language/LanguageProfileItemDragPreview.js +++ /dev/null @@ -1,88 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { DragLayer } from 'react-dnd'; -import DragPreviewLayer from 'Components/DragPreviewLayer'; -import { QUALITY_PROFILE_ITEM } from 'Helpers/dragTypes'; -import dimensions from 'Styles/Variables/dimensions.js'; -import LanguageProfileItem from './LanguageProfileItem'; -import styles from './LanguageProfileItemDragPreview.css'; - -const formGroupSmallWidth = parseInt(dimensions.formGroupSmallWidth); -const formLabelLargeWidth = parseInt(dimensions.formLabelLargeWidth); -const formLabelRightMarginWidth = parseInt(dimensions.formLabelRightMarginWidth); -const dragHandleWidth = parseInt(dimensions.dragHandleWidth); - -function collectDragLayer(monitor) { - return { - item: monitor.getItem(), - itemType: monitor.getItemType(), - currentOffset: monitor.getSourceClientOffset() - }; -} - -class LanguageProfileItemDragPreview extends Component { - - // - // Render - - render() { - const { - item, - itemType, - currentOffset - } = this.props; - - if (!currentOffset || itemType !== QUALITY_PROFILE_ITEM) { - return null; - } - - // The offset is shifted because the drag handle is on the right edge of the - // list item and the preview is wider than the drag handle. - - const { x, y } = currentOffset; - const handleOffset = formGroupSmallWidth - formLabelLargeWidth - formLabelRightMarginWidth - dragHandleWidth; - const transform = `translate3d(${x - handleOffset}px, ${y}px, 0)`; - - const style = { - position: 'absolute', - WebkitTransform: transform, - msTransform: transform, - transform - }; - - const { - languageId, - name, - allowed, - sortIndex - } = item; - - return ( - -
- -
-
- ); - } -} - -LanguageProfileItemDragPreview.propTypes = { - item: PropTypes.object, - itemType: PropTypes.string, - currentOffset: PropTypes.shape({ - x: PropTypes.number.isRequired, - y: PropTypes.number.isRequired - }) -}; - -export default DragLayer(collectDragLayer)(LanguageProfileItemDragPreview); diff --git a/frontend/src/Settings/Profiles/Language/LanguageProfileItemDragSource.css b/frontend/src/Settings/Profiles/Language/LanguageProfileItemDragSource.css deleted file mode 100644 index f59379129..000000000 --- a/frontend/src/Settings/Profiles/Language/LanguageProfileItemDragSource.css +++ /dev/null @@ -1,18 +0,0 @@ -.languageProfileItemDragSource { - padding: 4px 0; -} - -.languageProfileItemPlaceholder { - width: 100%; - height: 36px; - border: 1px dotted #aaa; - border-radius: 4px; -} - -.languageProfileItemPlaceholderBefore { - margin-bottom: 8px; -} - -.languageProfileItemPlaceholderAfter { - margin-top: 8px; -} diff --git a/frontend/src/Settings/Profiles/Language/LanguageProfileItemDragSource.js b/frontend/src/Settings/Profiles/Language/LanguageProfileItemDragSource.js deleted file mode 100644 index 1c5d3e061..000000000 --- a/frontend/src/Settings/Profiles/Language/LanguageProfileItemDragSource.js +++ /dev/null @@ -1,157 +0,0 @@ -import classNames from 'classnames'; -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { DragSource, DropTarget } from 'react-dnd'; -import { findDOMNode } from 'react-dom'; -import { QUALITY_PROFILE_ITEM } from 'Helpers/dragTypes'; -import LanguageProfileItem from './LanguageProfileItem'; -import styles from './LanguageProfileItemDragSource.css'; - -const languageProfileItemDragSource = { - beginDrag({ languageId, name, allowed, sortIndex }) { - return { - languageId, - name, - allowed, - sortIndex - }; - }, - - endDrag(props, monitor, component) { - props.onLanguageProfileItemDragEnd(monitor.getItem(), monitor.didDrop()); - } -}; - -const languageProfileItemDropTarget = { - hover(props, monitor, component) { - const dragIndex = monitor.getItem().sortIndex; - const hoverIndex = props.sortIndex; - - const hoverBoundingRect = findDOMNode(component).getBoundingClientRect(); - const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; - const clientOffset = monitor.getClientOffset(); - const hoverClientY = clientOffset.y - hoverBoundingRect.top; - - // Moving up, only trigger if drag position is above 50% - if (dragIndex < hoverIndex && hoverClientY > hoverMiddleY) { - return; - } - - // Moving down, only trigger if drag position is below 50% - if (dragIndex > hoverIndex && hoverClientY < hoverMiddleY) { - return; - } - - props.onLanguageProfileItemDragMove(dragIndex, hoverIndex); - } -}; - -function collectDragSource(connect, monitor) { - return { - connectDragSource: connect.dragSource(), - isDragging: monitor.isDragging() - }; -} - -function collectDropTarget(connect, monitor) { - return { - connectDropTarget: connect.dropTarget(), - isOver: monitor.isOver() - }; -} - -class LanguageProfileItemDragSource extends Component { - - // - // Render - - render() { - const { - languageId, - name, - allowed, - sortIndex, - isDragging, - isDraggingUp, - isDraggingDown, - isOver, - connectDragSource, - connectDropTarget, - onLanguageProfileItemAllowedChange - } = this.props; - - const isBefore = !isDragging && isDraggingUp && isOver; - const isAfter = !isDragging && isDraggingDown && isOver; - - // if (isDragging && !isOver) { - // return null; - // } - - return connectDropTarget( -
- { - isBefore && -
- } - - - - { - isAfter && -
- } -
- ); - } -} - -LanguageProfileItemDragSource.propTypes = { - languageId: PropTypes.number.isRequired, - name: PropTypes.string.isRequired, - allowed: PropTypes.bool.isRequired, - sortIndex: PropTypes.number.isRequired, - isDragging: PropTypes.bool, - isDraggingUp: PropTypes.bool, - isDraggingDown: PropTypes.bool, - isOver: PropTypes.bool, - connectDragSource: PropTypes.func, - connectDropTarget: PropTypes.func, - onLanguageProfileItemAllowedChange: PropTypes.func.isRequired, - onLanguageProfileItemDragMove: PropTypes.func.isRequired, - onLanguageProfileItemDragEnd: PropTypes.func.isRequired -}; - -export default DropTarget( - QUALITY_PROFILE_ITEM, - languageProfileItemDropTarget, - collectDropTarget -)(DragSource( - QUALITY_PROFILE_ITEM, - languageProfileItemDragSource, - collectDragSource -)(LanguageProfileItemDragSource)); diff --git a/frontend/src/Settings/Profiles/Language/LanguageProfileItems.css b/frontend/src/Settings/Profiles/Language/LanguageProfileItems.css deleted file mode 100644 index 48b30f326..000000000 --- a/frontend/src/Settings/Profiles/Language/LanguageProfileItems.css +++ /dev/null @@ -1,6 +0,0 @@ -.languages { - margin-top: 10px; - /* TODO: This should consider the number of languages in the list */ - min-height: 550px; - user-select: none; -} diff --git a/frontend/src/Settings/Profiles/Language/LanguageProfileItems.js b/frontend/src/Settings/Profiles/Language/LanguageProfileItems.js deleted file mode 100644 index c8e1568d1..000000000 --- a/frontend/src/Settings/Profiles/Language/LanguageProfileItems.js +++ /dev/null @@ -1,103 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import FormGroup from 'Components/Form/FormGroup'; -import FormInputHelpText from 'Components/Form/FormInputHelpText'; -import FormLabel from 'Components/Form/FormLabel'; -import LanguageProfileItemDragPreview from './LanguageProfileItemDragPreview'; -import LanguageProfileItemDragSource from './LanguageProfileItemDragSource'; -import styles from './LanguageProfileItems.css'; - -class LanguageProfileItems extends Component { - - // - // Render - - render() { - const { - dragIndex, - dropIndex, - languageProfileItems, - errors, - warnings, - ...otherProps - } = this.props; - - const isDragging = dropIndex !== null; - const isDraggingUp = isDragging && dropIndex > dragIndex; - const isDraggingDown = isDragging && dropIndex < dragIndex; - - return ( - - Languages -
- - - { - errors.map((error, index) => { - return ( - - ); - }) - } - - { - warnings.map((warning, index) => { - return ( - - ); - }) - } - -
- { - languageProfileItems.map(({ allowed, language }, index) => { - return ( - - ); - }).reverse() - } - - -
-
-
- ); - } -} - -LanguageProfileItems.propTypes = { - dragIndex: PropTypes.number, - dropIndex: PropTypes.number, - languageProfileItems: PropTypes.arrayOf(PropTypes.object).isRequired, - errors: PropTypes.arrayOf(PropTypes.object), - warnings: PropTypes.arrayOf(PropTypes.object) -}; - -LanguageProfileItems.defaultProps = { - errors: [], - warnings: [] -}; - -export default LanguageProfileItems; diff --git a/frontend/src/Settings/Profiles/Language/LanguageProfileNameConnector.js b/frontend/src/Settings/Profiles/Language/LanguageProfileNameConnector.js deleted file mode 100644 index 61a7153b5..000000000 --- a/frontend/src/Settings/Profiles/Language/LanguageProfileNameConnector.js +++ /dev/null @@ -1,31 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import createLanguageProfileSelector from 'Store/Selectors/createLanguageProfileSelector'; - -function createMapStateToProps() { - return createSelector( - createLanguageProfileSelector(), - (languageProfile) => { - return { - name: languageProfile.name - }; - } - ); -} - -function LanguageProfileNameConnector({ name, ...otherProps }) { - return ( - - {name} - - ); -} - -LanguageProfileNameConnector.propTypes = { - languageProfileId: PropTypes.number.isRequired, - name: PropTypes.string.isRequired -}; - -export default connect(createMapStateToProps)(LanguageProfileNameConnector); diff --git a/frontend/src/Settings/Profiles/Language/LanguageProfiles.css b/frontend/src/Settings/Profiles/Language/LanguageProfiles.css deleted file mode 100644 index 440adff4b..000000000 --- a/frontend/src/Settings/Profiles/Language/LanguageProfiles.css +++ /dev/null @@ -1,21 +0,0 @@ -.languageProfiles { - display: flex; - flex-wrap: wrap; -} - -.addLanguageProfile { - composes: languageProfile from '~./LanguageProfile.css'; - - background-color: var(--cardAlternateBackgroundColor); - color: var(--gray); - text-align: center; - font-size: 45px; -} - -.center { - display: inline-block; - padding: 5px 20px 0; - border: 1px solid var(--borderColor); - border-radius: 4px; - background-color: var(--cardCenterBackgroundColor); -} diff --git a/frontend/src/Settings/Profiles/Language/LanguageProfiles.js b/frontend/src/Settings/Profiles/Language/LanguageProfiles.js deleted file mode 100644 index aef957049..000000000 --- a/frontend/src/Settings/Profiles/Language/LanguageProfiles.js +++ /dev/null @@ -1,107 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import Card from 'Components/Card'; -import FieldSet from 'Components/FieldSet'; -import Icon from 'Components/Icon'; -import PageSectionContent from 'Components/Page/PageSectionContent'; -import { icons } from 'Helpers/Props'; -import EditLanguageProfileModalConnector from './EditLanguageProfileModalConnector'; -import LanguageProfile from './LanguageProfile'; -import styles from './LanguageProfiles.css'; - -class LanguageProfiles extends Component { - - // - // Lifecycle - - constructor(props, context) { - super(props, context); - - this.state = { - isLanguageProfileModalOpen: false - }; - } - - // - // Listeners - - onCloneLanguageProfilePress = (id) => { - this.props.onCloneLanguageProfilePress(id); - this.setState({ isLanguageProfileModalOpen: true }); - }; - - onEditLanguageProfilePress = () => { - this.setState({ isLanguageProfileModalOpen: true }); - }; - - onModalClose = () => { - this.setState({ isLanguageProfileModalOpen: false }); - }; - - // - // Render - - render() { - const { - items, - isDeleting, - onConfirmDeleteLanguageProfile, - onCloneLanguageProfilePress, - ...otherProps - } = this.props; - - return ( -
- -
- { - items.map((item) => { - return ( - - ); - }) - } - - -
- -
-
-
- - -
-
- ); - } -} - -LanguageProfiles.propTypes = { - advancedSettings: PropTypes.bool.isRequired, - isFetching: PropTypes.bool.isRequired, - error: PropTypes.object, - items: PropTypes.arrayOf(PropTypes.object).isRequired, - isDeleting: PropTypes.bool.isRequired, - onConfirmDeleteLanguageProfile: PropTypes.func.isRequired, - onCloneLanguageProfilePress: PropTypes.func.isRequired -}; - -export default LanguageProfiles; diff --git a/frontend/src/Settings/Profiles/Language/LanguageProfilesConnector.js b/frontend/src/Settings/Profiles/Language/LanguageProfilesConnector.js deleted file mode 100644 index 0b9d61d3f..000000000 --- a/frontend/src/Settings/Profiles/Language/LanguageProfilesConnector.js +++ /dev/null @@ -1,69 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import { cloneLanguageProfile, deleteLanguageProfile, fetchLanguageProfiles } from 'Store/Actions/settingsActions'; -import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector'; -import sortByName from 'Utilities/Array/sortByName'; -import LanguageProfiles from './LanguageProfiles'; - -function createMapStateToProps() { - return createSelector( - (state) => state.settings.advancedSettings, - createSortedSectionSelector('settings.languageProfiles', sortByName), - (advancedSettings, languageProfiles) => { - return { - advancedSettings, - ...languageProfiles - }; - } - ); -} - -const mapDispatchToProps = { - dispatchFetchLanguageProfiles: fetchLanguageProfiles, - dispatchDeleteLanguageProfile: deleteLanguageProfile, - dispatchCloneLanguageProfile: cloneLanguageProfile -}; - -class LanguageProfilesConnector extends Component { - - // - // Lifecycle - - componentDidMount() { - this.props.dispatchFetchLanguageProfiles(); - } - - // - // Listeners - - onConfirmDeleteLanguageProfile = (id) => { - this.props.dispatchDeleteLanguageProfile({ id }); - }; - - onCloneLanguageProfilePress = (id) => { - this.props.dispatchCloneLanguageProfile({ id }); - }; - - // - // Render - - render() { - return ( - - ); - } -} - -LanguageProfilesConnector.propTypes = { - dispatchFetchLanguageProfiles: PropTypes.func.isRequired, - dispatchDeleteLanguageProfile: PropTypes.func.isRequired, - dispatchCloneLanguageProfile: PropTypes.func.isRequired -}; - -export default connect(createMapStateToProps, mapDispatchToProps)(LanguageProfilesConnector); diff --git a/frontend/src/Settings/Profiles/Profiles.js b/frontend/src/Settings/Profiles/Profiles.js index 7196d9f0d..642466aaa 100644 --- a/frontend/src/Settings/Profiles/Profiles.js +++ b/frontend/src/Settings/Profiles/Profiles.js @@ -5,7 +5,6 @@ import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; import DelayProfilesConnector from './Delay/DelayProfilesConnector'; -import LanguageProfilesConnector from './Language/LanguageProfilesConnector'; import QualityProfilesConnector from './Quality/QualityProfilesConnector'; import ReleaseProfilesConnector from './Release/ReleaseProfilesConnector'; @@ -25,7 +24,6 @@ class Profiles extends Component { - diff --git a/frontend/src/Settings/UI/UISettingsConnector.js b/frontend/src/Settings/UI/UISettingsConnector.js index 7b7846415..66d03d226 100644 --- a/frontend/src/Settings/UI/UISettingsConnector.js +++ b/frontend/src/Settings/UI/UISettingsConnector.js @@ -3,7 +3,7 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import { clearPendingChanges } from 'Store/Actions/baseActions'; -import { fetchLanguageProfileSchema, fetchUISettings, saveUISettings, setUISettingsValue } from 'Store/Actions/settingsActions'; +import { fetchUISettings, saveUISettings, setUISettingsValue } from 'Store/Actions/settingsActions'; import createLanguagesSelector from 'Store/Selectors/createLanguagesSelector'; import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector'; import UISettings from './UISettings'; @@ -20,11 +20,11 @@ function createFilteredLanguagesSelector() { } const newItems = languages.items - .filter((lang) => !FILTER_LANGUAGES.includes(lang.language.name)) + .filter((lang) => !FILTER_LANGUAGES.includes(lang.name)) .map((item) => { return { - key: item.language.id, - value: item.language.name + key: item.id, + value: item.name }; }); @@ -58,8 +58,7 @@ const mapDispatchToProps = { dispatchSetUISettingsValue: setUISettingsValue, dispatchSaveUISettings: saveUISettings, dispatchFetchUISettings: fetchUISettings, - dispatchClearPendingChanges: clearPendingChanges, - dispatchFetchLanguageProfileSchema: fetchLanguageProfileSchema + dispatchClearPendingChanges: clearPendingChanges }; class UISettingsConnector extends Component { @@ -69,16 +68,10 @@ class UISettingsConnector extends Component { componentDidMount() { const { - isLanguagesPopulated, - dispatchFetchUISettings, - dispatchFetchLanguageProfileSchema + dispatchFetchUISettings } = this.props; dispatchFetchUISettings(); - - if (!isLanguagesPopulated) { - dispatchFetchLanguageProfileSchema(); - } } componentWillUnmount() { @@ -115,8 +108,7 @@ UISettingsConnector.propTypes = { dispatchSetUISettingsValue: PropTypes.func.isRequired, dispatchSaveUISettings: PropTypes.func.isRequired, dispatchFetchUISettings: PropTypes.func.isRequired, - dispatchClearPendingChanges: PropTypes.func.isRequired, - dispatchFetchLanguageProfileSchema: PropTypes.func.isRequired + dispatchClearPendingChanges: PropTypes.func.isRequired }; export default connect(createMapStateToProps, mapDispatchToProps)(UISettingsConnector); diff --git a/frontend/src/Store/Actions/Settings/languageProfiles.js b/frontend/src/Store/Actions/Settings/languageProfiles.js deleted file mode 100644 index b407f1089..000000000 --- a/frontend/src/Store/Actions/Settings/languageProfiles.js +++ /dev/null @@ -1,97 +0,0 @@ -import { createAction } from 'redux-actions'; -import createFetchHandler from 'Store/Actions/Creators/createFetchHandler'; -import createFetchSchemaHandler from 'Store/Actions/Creators/createFetchSchemaHandler'; -import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler'; -import createSaveProviderHandler from 'Store/Actions/Creators/createSaveProviderHandler'; -import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer'; -import { createThunk } from 'Store/thunks'; -import getSectionState from 'Utilities/State/getSectionState'; -import updateSectionState from 'Utilities/State/updateSectionState'; - -// -// Variables - -const section = 'settings.languageProfiles'; - -// -// Actions Types - -export const FETCH_LANGUAGE_PROFILES = 'settings/languageProfiles/fetchLanguageProfiles'; -export const FETCH_LANGUAGE_PROFILE_SCHEMA = 'settings/languageProfiles/fetchLanguageProfileSchema'; -export const SAVE_LANGUAGE_PROFILE = 'settings/languageProfiles/saveLanguageProfile'; -export const DELETE_LANGUAGE_PROFILE = 'settings/languageProfiles/deleteLanguageProfile'; -export const SET_LANGUAGE_PROFILE_VALUE = 'settings/languageProfiles/setLanguageProfileValue'; -export const CLONE_LANGUAGE_PROFILE = 'settings/languageProfiles/cloneLanguageProfile'; - -// -// Action Creators - -export const fetchLanguageProfiles = createThunk(FETCH_LANGUAGE_PROFILES); -export const fetchLanguageProfileSchema = createThunk(FETCH_LANGUAGE_PROFILE_SCHEMA); -export const saveLanguageProfile = createThunk(SAVE_LANGUAGE_PROFILE); -export const deleteLanguageProfile = createThunk(DELETE_LANGUAGE_PROFILE); - -export const setLanguageProfileValue = createAction(SET_LANGUAGE_PROFILE_VALUE, (payload) => { - return { - section, - ...payload - }; -}); - -export const cloneLanguageProfile = createAction(CLONE_LANGUAGE_PROFILE); - -// -// Details - -export default { - - // - // State - - defaultState: { - isFetching: false, - isPopulated: false, - error: null, - isDeleting: false, - deleteError: null, - isSchemaFetching: false, - isSchemaPopulated: false, - schemaError: null, - schema: {}, - isSaving: false, - saveError: null, - items: [], - pendingChanges: {} - }, - - // - // Action Handlers - - actionHandlers: { - [FETCH_LANGUAGE_PROFILES]: createFetchHandler(section, '/languageprofile'), - [FETCH_LANGUAGE_PROFILE_SCHEMA]: createFetchSchemaHandler(section, '/languageprofile/schema'), - [SAVE_LANGUAGE_PROFILE]: createSaveProviderHandler(section, '/languageprofile'), - [DELETE_LANGUAGE_PROFILE]: createRemoveItemHandler(section, '/languageprofile') - }, - - // - // Reducers - - reducers: { - [SET_LANGUAGE_PROFILE_VALUE]: createSetSettingValueReducer(section), - - [CLONE_LANGUAGE_PROFILE]: function(state, { payload }) { - const id = payload.id; - const newState = getSectionState(state, section); - const item = newState.items.find((i) => i.id === id); - const pendingChanges = { ...item, id: 0 }; - delete pendingChanges.id; - - pendingChanges.name = `${pendingChanges.name} - Copy`; - newState.pendingChanges = pendingChanges; - - return updateSectionState(state, section, newState); - } - } - -}; diff --git a/frontend/src/Store/Actions/Settings/languages.js b/frontend/src/Store/Actions/Settings/languages.js new file mode 100644 index 000000000..a0b62fc49 --- /dev/null +++ b/frontend/src/Store/Actions/Settings/languages.js @@ -0,0 +1,48 @@ +import createFetchHandler from 'Store/Actions/Creators/createFetchHandler'; +import { createThunk } from 'Store/thunks'; + +// +// Variables + +const section = 'settings.languages'; + +// +// Actions Types + +export const FETCH_LANGUAGES = 'settings/languages/fetchLanguages'; + +// +// Action Creators + +export const fetchLanguages = createThunk(FETCH_LANGUAGES); + +// +// Details + +export default { + + // + // State + + defaultState: { + isFetching: false, + isPopulated: false, + error: null, + items: [] + }, + + // + // Action Handlers + + actionHandlers: { + [FETCH_LANGUAGES]: createFetchHandler(section, '/language') + }, + + // + // Reducers + + reducers: { + + } + +}; diff --git a/frontend/src/Store/Actions/addSeriesActions.js b/frontend/src/Store/Actions/addSeriesActions.js index f07ba8fdf..d0399b22b 100644 --- a/frontend/src/Store/Actions/addSeriesActions.js +++ b/frontend/src/Store/Actions/addSeriesActions.js @@ -34,7 +34,6 @@ export const defaultState = { rootFolderPath: '', monitor: monitorOptions[0].key, qualityProfileId: 0, - languageProfileId: 0, seriesType: seriesTypes.STANDARD, seasonFolder: true, searchForMissingEpisodes: false, diff --git a/frontend/src/Store/Actions/blocklistActions.js b/frontend/src/Store/Actions/blocklistActions.js index 5be0797cc..95c30d712 100644 --- a/frontend/src/Store/Actions/blocklistActions.js +++ b/frontend/src/Store/Actions/blocklistActions.js @@ -43,8 +43,8 @@ export const defaultState = { isVisible: true }, { - name: 'language', - label: 'Language', + name: 'languages', + label: 'Languages', isVisible: false }, { diff --git a/frontend/src/Store/Actions/episodeActions.js b/frontend/src/Store/Actions/episodeActions.js index d2b77a03a..0e6113247 100644 --- a/frontend/src/Store/Actions/episodeActions.js +++ b/frontend/src/Store/Actions/episodeActions.js @@ -60,8 +60,8 @@ export const defaultState = { isVisible: true }, { - name: 'language', - label: 'Language', + name: 'languages', + label: 'Languages', isVisible: false }, { diff --git a/frontend/src/Store/Actions/episodeFileActions.js b/frontend/src/Store/Actions/episodeFileActions.js index 0ce3d9762..7f0748967 100644 --- a/frontend/src/Store/Actions/episodeFileActions.js +++ b/frontend/src/Store/Actions/episodeFileActions.js @@ -162,7 +162,7 @@ export const actionHandlers = handleThunks({ props.qualityCutoffNotMet = episodeFile.qualityCutoffNotMet; props.languageCutoffNotMet = episodeFile.languageCutoffNotMet; - props.language = file.language; + props.languages = file.languages; props.quality = file.quality; props.releaseGroup = file.releaseGroup; diff --git a/frontend/src/Store/Actions/historyActions.js b/frontend/src/Store/Actions/historyActions.js index 85fe95208..5b6ef8851 100644 --- a/frontend/src/Store/Actions/historyActions.js +++ b/frontend/src/Store/Actions/historyActions.js @@ -52,8 +52,8 @@ export const defaultState = { isVisible: true }, { - name: 'language', - label: 'Language', + name: 'languages', + label: 'Languages', isVisible: false }, { diff --git a/frontend/src/Store/Actions/interactiveImportActions.js b/frontend/src/Store/Actions/interactiveImportActions.js index c55e891f8..a1eccef7d 100644 --- a/frontend/src/Store/Actions/interactiveImportActions.js +++ b/frontend/src/Store/Actions/interactiveImportActions.js @@ -177,7 +177,7 @@ export const actionHandlers = handleThunks({ seasonNumber: item.seasonNumber, episodeIds: (item.episodes || []).map((e) => e.id), quality: item.quality, - language: item.language, + languages: item.languages, releaseGroup: item.releaseGroup, downloadId: item.downloadId }; diff --git a/frontend/src/Store/Actions/queueActions.js b/frontend/src/Store/Actions/queueActions.js index d5f89d2bc..aa351d152 100644 --- a/frontend/src/Store/Actions/queueActions.js +++ b/frontend/src/Store/Actions/queueActions.js @@ -87,8 +87,8 @@ export const defaultState = { isVisible: false }, { - name: 'language', - label: 'Language', + name: 'languages', + label: 'Languages', isSortable: true, isVisible: false }, diff --git a/frontend/src/Store/Actions/seriesActions.js b/frontend/src/Store/Actions/seriesActions.js index 813f65572..0f7a3ff33 100644 --- a/frontend/src/Store/Actions/seriesActions.js +++ b/frontend/src/Store/Actions/seriesActions.js @@ -204,12 +204,6 @@ export const filterBuilderProps = [ type: filterBuilderTypes.EXACT, valueType: filterBuilderValueTypes.QUALITY_PROFILE }, - { - name: 'languageProfileId', - label: 'Language Profile', - type: filterBuilderTypes.EXACT, - valueType: filterBuilderValueTypes.LANGUAGE_PROFILE - }, { name: 'nextAiring', label: 'Next Airing', diff --git a/frontend/src/Store/Actions/seriesEditorActions.js b/frontend/src/Store/Actions/seriesEditorActions.js index ba976edd0..cfcd5e626 100644 --- a/frontend/src/Store/Actions/seriesEditorActions.js +++ b/frontend/src/Store/Actions/seriesEditorActions.js @@ -53,12 +53,6 @@ export const defaultState = { isSortable: true, isVisible: true }, - { - name: 'languageProfileId', - label: 'Language Profile', - isSortable: true, - isVisible: true - }, { name: 'seriesType', label: 'Type', diff --git a/frontend/src/Store/Actions/seriesIndexActions.js b/frontend/src/Store/Actions/seriesIndexActions.js index 711344d23..0e1c4d9e5 100644 --- a/frontend/src/Store/Actions/seriesIndexActions.js +++ b/frontend/src/Store/Actions/seriesIndexActions.js @@ -83,12 +83,6 @@ export const defaultState = { isSortable: true, isVisible: true }, - { - name: 'languageProfileId', - label: 'Language Profile', - isSortable: true, - isVisible: false - }, { name: 'nextAiring', label: 'Next Airing', diff --git a/frontend/src/Store/Actions/settingsActions.js b/frontend/src/Store/Actions/settingsActions.js index b33abfd69..54dbf1206 100644 --- a/frontend/src/Store/Actions/settingsActions.js +++ b/frontend/src/Store/Actions/settingsActions.js @@ -11,7 +11,7 @@ import importListExclusions from './Settings/importListExclusions'; import importLists from './Settings/importLists'; import indexerOptions from './Settings/indexerOptions'; import indexers from './Settings/indexers'; -import languageProfiles from './Settings/languageProfiles'; +import languages from './Settings/languages'; import mediaManagement from './Settings/mediaManagement'; import metadata from './Settings/metadata'; import naming from './Settings/naming'; @@ -33,7 +33,7 @@ export * from './Settings/importLists'; export * from './Settings/importListExclusions'; export * from './Settings/indexerOptions'; export * from './Settings/indexers'; -export * from './Settings/languageProfiles'; +export * from './Settings/languages'; export * from './Settings/mediaManagement'; export * from './Settings/metadata'; export * from './Settings/naming'; @@ -66,7 +66,7 @@ export const defaultState = { importListExclusions: importListExclusions.defaultState, indexerOptions: indexerOptions.defaultState, indexers: indexers.defaultState, - languageProfiles: languageProfiles.defaultState, + languages: languages.defaultState, mediaManagement: mediaManagement.defaultState, metadata: metadata.defaultState, naming: naming.defaultState, @@ -107,7 +107,7 @@ export const actionHandlers = handleThunks({ ...importListExclusions.actionHandlers, ...indexerOptions.actionHandlers, ...indexers.actionHandlers, - ...languageProfiles.actionHandlers, + ...languages.actionHandlers, ...mediaManagement.actionHandlers, ...metadata.actionHandlers, ...naming.actionHandlers, @@ -139,7 +139,7 @@ export const reducers = createHandleActions({ ...importListExclusions.reducers, ...indexerOptions.reducers, ...indexers.reducers, - ...languageProfiles.reducers, + ...languages.reducers, ...mediaManagement.reducers, ...metadata.reducers, ...naming.reducers, diff --git a/frontend/src/Store/Actions/wantedActions.js b/frontend/src/Store/Actions/wantedActions.js index fae855f56..db0815c8c 100644 --- a/frontend/src/Store/Actions/wantedActions.js +++ b/frontend/src/Store/Actions/wantedActions.js @@ -122,8 +122,8 @@ export const defaultState = { isVisible: true }, { - name: 'language', - label: 'Language', + name: 'languages', + label: 'Languages', isVisible: false }, { diff --git a/frontend/src/Store/Selectors/createLanguageProfileSelector.js b/frontend/src/Store/Selectors/createLanguageProfileSelector.js deleted file mode 100644 index c32f85ada..000000000 --- a/frontend/src/Store/Selectors/createLanguageProfileSelector.js +++ /dev/null @@ -1,15 +0,0 @@ -import { createSelector } from 'reselect'; - -function createLanguageProfileSelector() { - return createSelector( - (state, { languageProfileId }) => languageProfileId, - (state) => state.settings.languageProfiles.items, - (languageProfileId, languageProfiles) => { - return languageProfiles.find((profile) => { - return profile.id === languageProfileId; - }); - } - ); -} - -export default createLanguageProfileSelector; diff --git a/frontend/src/Store/Selectors/createLanguagesSelector.js b/frontend/src/Store/Selectors/createLanguagesSelector.js index 53de7d696..47840933c 100644 --- a/frontend/src/Store/Selectors/createLanguagesSelector.js +++ b/frontend/src/Store/Selectors/createLanguagesSelector.js @@ -2,20 +2,23 @@ import { createSelector } from 'reselect'; function createLanguagesSelector() { return createSelector( - (state) => state.settings.languageProfiles, - (languageProfiles) => { + (state) => state.settings.languages, + (languages) => { const { - isSchemaFetching: isFetching, - isSchemaPopulated: isPopulated, - schemaError: error, - schema - } = languageProfiles; + isFetching, + isPopulated, + error, + items + } = languages; + + const filterItems = ['Any']; + const filteredLanguages = items.filter((lang) => !filterItems.includes(lang.name)); return { isFetching, isPopulated, error, - items: schema.languages ? [...schema.languages].reverse() : [] + items: filteredLanguages }; } ); diff --git a/frontend/src/Store/Selectors/createSeriesLanguageProfileSelector.js b/frontend/src/Store/Selectors/createSeriesLanguageProfileSelector.js deleted file mode 100644 index 94ebcc0ec..000000000 --- a/frontend/src/Store/Selectors/createSeriesLanguageProfileSelector.js +++ /dev/null @@ -1,16 +0,0 @@ -import { createSelector } from 'reselect'; -import createSeriesSelector from './createSeriesSelector'; - -function createSeriesLanguageProfileSelector() { - return createSelector( - (state) => state.settings.languageProfiles.items, - createSeriesSelector(), - (languageProfiles, series = {}) => { - return languageProfiles.find((profile) => { - return profile.id === series.languageProfileId; - }); - } - ); -} - -export default createSeriesLanguageProfileSelector; diff --git a/frontend/src/Utilities/Series/getNewSeries.js b/frontend/src/Utilities/Series/getNewSeries.js index 142819d38..6e4de0c74 100644 --- a/frontend/src/Utilities/Series/getNewSeries.js +++ b/frontend/src/Utilities/Series/getNewSeries.js @@ -4,7 +4,6 @@ function getNewSeries(series, payload) { rootFolderPath, monitor, qualityProfileId, - languageProfileId, seriesType, seasonFolder, tags, @@ -21,7 +20,6 @@ function getNewSeries(series, payload) { series.addOptions = addOptions; series.monitored = true; series.qualityProfileId = qualityProfileId; - series.languageProfileId = languageProfileId; series.rootFolderPath = rootFolderPath; series.seriesType = seriesType; series.seasonFolder = seasonFolder; diff --git a/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.css b/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.css index d033cb221..55b33c37a 100644 --- a/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.css +++ b/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.css @@ -1,5 +1,5 @@ .episode, -.language, +.languages, .status { composes: cell from '~Components/Table/Cells/TableRowCell.css'; diff --git a/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.js b/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.js index 277c4c329..7d98eaee3 100644 --- a/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.js +++ b/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.js @@ -106,11 +106,11 @@ function CutoffUnmetRow(props) { ); } - if (name === 'language') { + if (name === 'languages') { return ( { 1 }, Quality = new QualityModel(Quality.Bluray720p), - Language = Core.Languages.Language.English, + Languages = new List { Language.English }, SourceTitle = "series.title.s01e01", Date = DateTime.UtcNow }; diff --git a/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs b/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs index 4d540a032..4a1226e1d 100644 --- a/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; @@ -18,9 +19,8 @@ public class DatabaseRelationshipFixture : DbTest public void one_to_one() { var episodeFile = Builder.CreateNew() - .With(c => c.Language = Language.English) + .With(c => c.Languages = new List { Language.English }) .With(c => c.Quality = new QualityModel()) - .With(c => c.Language = Language.English) .BuildNew(); Db.Insert(episodeFile); @@ -61,7 +61,7 @@ public void embedded_document_as_json() var quality = new QualityModel { Quality = Quality.Bluray720p, Revision = new Revision(version: 2) }; var history = Builder.CreateNew() - .With(c => c.Language = Language.English) + .With(c => c.Languages = new List { Language.English }) .With(c => c.Id = 0) .With(c => c.Quality = quality) .Build(); @@ -78,7 +78,7 @@ public void embedded_list_of_document_with_json() var history = Builder.CreateListOfSize(2) .All() .With(c => c.Id = 0) - .With(c => c.Language = Language.English) + .With(c => c.Languages = new List { Language.English }) .Build().ToList(); history[0].Quality = new QualityModel(Quality.HDTV1080p, new Revision(version: 2)); diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/161_fix_pending_releasesFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/161_fix_pending_releasesFixture.cs index a77097adb..aebca15ba 100644 --- a/src/NzbDrone.Core.Test/Datastore/Migration/161_fix_pending_releasesFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Migration/161_fix_pending_releasesFixture.cs @@ -1,8 +1,11 @@ using System; +using System.Collections.Generic; using System.Linq; using Dapper; using FluentAssertions; using NUnit.Framework; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Datastore.Converters; using NzbDrone.Core.Datastore.Migration; using NzbDrone.Core.Download.Pending; using NzbDrone.Core.Languages; @@ -18,6 +21,8 @@ public class fix_pending_releasesFixture : MigrationTest()); + var db = WithDapperMigrationTestDb(c => { c.Insert.IntoTable("PendingReleases").Row(new @@ -77,9 +82,54 @@ public void should_fix_quality_for_pending_releases() var json = db.Query("SELECT ParsedEpisodeInfo FROM PendingReleases").First(); - var pending = db.Query("SELECT ParsedEpisodeInfo FROM PendingReleases").First(); - pending.Quality.Quality.Should().Be(Quality.HDTV720p); - pending.Language.Should().Be(Language.English); + var pending = db.Query("SELECT ParsedEpisodeInfo FROM PendingReleases").First(); + pending.Quality.Quality.Should().Be(Quality.HDTV720p.Id); + pending.Language.Should().Be(Language.English.Id); + } + + private class SeriesTitleInfo161 + { + public string Title { get; set; } + public string TitleWithoutYear { get; set; } + public int Year { get; set; } + } + + private class ParsedEpisodeInfo162 + { + public string SeriesTitle { get; set; } + public SeriesTitleInfo161 SeriesTitleInfo { get; set; } + public QualityModel162 Quality { get; set; } + public int SeasonNumber { get; set; } + public List EpisodeNumbers { get; set; } + public List AbsoluteEpisodeNumbers { get; set; } + public List SpecialAbsoluteEpisodeNumbers { get; set; } + public int Language { get; set; } + public bool FullSeason { get; set; } + public bool IsPartialSeason { get; set; } + public bool IsMultiSeason { get; set; } + public bool IsSeasonExtra { get; set; } + public bool Speacial { get; set; } + public string ReleaseGroup { get; set; } + public string ReleaseHash { get; set; } + public int SeasonPart { get; set; } + public string ReleaseTokens { get; set; } + public bool IsDaily { get; set; } + public bool IsAbsoluteNumbering { get; set; } + public bool IsPossibleSpecialEpisode { get; set; } + public bool IsPossibleSceneSeasonSpecial { get; set; } + } + + private class QualityModel162 + { + public int Quality { get; set; } + public Revision162 Revision { get; set; } + } + + private class Revision162 + { + public int Version { get; set; } + public int Real { get; set; } + public bool IsRepack { get; set; } } } } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs index df8828cd5..dbb0e77ca 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs @@ -13,7 +13,6 @@ using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Profiles; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.CustomFormats; @@ -54,16 +53,9 @@ private void GivenProfile(QualityProfile profile) Console.WriteLine(profile.ToJson()); } - private void GivenLanguageProfile(LanguageProfile profile) - { - _remoteMovie.Series.LanguageProfile = profile; - - Console.WriteLine(profile.ToJson()); - } - private void GivenFileQuality(QualityModel quality, Language language) { - _remoteMovie.Episodes.First().EpisodeFile = Builder.CreateNew().With(x => x.Quality = quality).With(x => x.Language = language).Build(); + _remoteMovie.Episodes.First().EpisodeFile = Builder.CreateNew().With(x => x.Quality = quality).With(x => x.Languages = new List { language }).Build(); } private void GivenNewQuality(QualityModel quality) @@ -100,13 +92,6 @@ public void should_return_true_if_current_episode_is_less_than_cutoff() UpgradeAllowed = true }); - GivenLanguageProfile(new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(Language.English), - Cutoff = Language.English, - UpgradeAllowed = true - }); - GivenFileQuality(new QualityModel(Quality.DVD, new Revision(version: 2)), Language.English); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); } @@ -121,13 +106,6 @@ public void should_return_false_if_current_episode_is_equal_to_cutoff() UpgradeAllowed = true }); - GivenLanguageProfile(new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(Language.English), - Cutoff = Language.English, - UpgradeAllowed = true - }); - GivenFileQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 2)), Language.English); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); } @@ -142,13 +120,6 @@ public void should_return_false_if_current_episode_is_greater_than_cutoff() UpgradeAllowed = true }); - GivenLanguageProfile(new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(Language.English), - Cutoff = Language.English, - UpgradeAllowed = true - }); - GivenFileQuality(new QualityModel(Quality.Bluray1080p, new Revision(version: 2)), Language.English); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); } @@ -163,13 +134,6 @@ public void should_return_true_when_new_episode_is_proper_but_existing_is_not() UpgradeAllowed = true }); - GivenLanguageProfile(new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(Language.English), - Cutoff = Language.English, - UpgradeAllowed = true - }); - GivenFileQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 1)), Language.English); GivenNewQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 2))); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); @@ -185,40 +149,11 @@ public void should_return_false_if_cutoff_is_met_and_quality_is_higher() UpgradeAllowed = true }); - GivenLanguageProfile(new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(Language.English), - Cutoff = Language.English, - UpgradeAllowed = true - }); - GivenFileQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 2)), Language.English); GivenNewQuality(new QualityModel(Quality.Bluray1080p, new Revision(version: 2))); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); } - [Test] - public void should_return_true_if_quality_cutoff_is_met_and_quality_is_higher_but_language_is_not_met() - { - GivenProfile(new QualityProfile - { - Cutoff = Quality.HDTV720p.Id, - Items = Qualities.QualityFixture.GetDefaultQualities(), - UpgradeAllowed = true - }); - - GivenLanguageProfile(new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(), - Cutoff = Language.Spanish, - UpgradeAllowed = true - }); - - GivenFileQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 2)), Language.English); - GivenNewQuality(new QualityModel(Quality.Bluray1080p, new Revision(version: 2))); - Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); - } - [Test] public void should_return_false_if_quality_cutoff_is_met_and_quality_is_higher_but_language_is_met() { @@ -229,13 +164,6 @@ public void should_return_false_if_quality_cutoff_is_met_and_quality_is_higher_b UpgradeAllowed = true }); - GivenLanguageProfile(new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(), - Cutoff = Language.Spanish, - UpgradeAllowed = true - }); - GivenFileQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 2)), Language.Spanish); GivenNewQuality(new QualityModel(Quality.Bluray1080p, new Revision(version: 2))); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); @@ -251,13 +179,6 @@ public void should_return_false_if_cutoff_is_met_and_quality_is_higher_and_langu UpgradeAllowed = true }); - GivenLanguageProfile(new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(), - Cutoff = Language.Spanish, - UpgradeAllowed = true - }); - GivenFileQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 2)), Language.French); GivenNewQuality(new QualityModel(Quality.Bluray1080p, new Revision(version: 2))); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); @@ -273,13 +194,6 @@ public void should_return_true_if_cutoff_is_not_met_and_new_quality_is_higher_an UpgradeAllowed = true }); - GivenLanguageProfile(new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(), - Cutoff = Language.Spanish, - UpgradeAllowed = true - }); - GivenFileQuality(new QualityModel(Quality.SDTV, new Revision(version: 2)), Language.French); GivenNewQuality(new QualityModel(Quality.Bluray1080p, new Revision(version: 2))); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); @@ -295,13 +209,6 @@ public void should_return_true_if_cutoff_is_not_met_and_language_is_higher() UpgradeAllowed = true }); - GivenLanguageProfile(new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(), - Cutoff = Language.Spanish, - UpgradeAllowed = true - }); - GivenFileQuality(new QualityModel(Quality.SDTV, new Revision(version: 2)), Language.French); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); } @@ -318,13 +225,6 @@ public void should_return_false_if_custom_formats_is_met_and_quality_and_format_ UpgradeAllowed = true }); - GivenLanguageProfile(new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(Language.English), - Cutoff = Language.English, - UpgradeAllowed = true - }); - GivenFileQuality(new QualityModel(Quality.HDTV720p), Language.English); GivenNewQuality(new QualityModel(Quality.Bluray1080p)); @@ -346,13 +246,6 @@ public void should_return_true_if_cutoffs_are_met_but_is_a_revision_upgrade() UpgradeAllowed = true }); - GivenLanguageProfile(new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(Language.English), - Cutoff = Language.English, - UpgradeAllowed = true - }); - GivenFileQuality(new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)), Language.English); GivenNewQuality(new QualityModel(Quality.WEBDL1080p, new Revision(version: 2))); @@ -369,13 +262,6 @@ public void should_return_false_if_quality_profile_does_not_allow_upgrades_but_c UpgradeAllowed = false }); - GivenLanguageProfile(new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(Language.English), - Cutoff = Language.English, - UpgradeAllowed = true - }); - GivenFileQuality(new QualityModel(Quality.WEBDL1080p), Language.English); GivenNewQuality(new QualityModel(Quality.Bluray1080p)); diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/LanguageSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/LanguageSpecificationFixture.cs deleted file mode 100644 index 821a8842e..000000000 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/LanguageSpecificationFixture.cs +++ /dev/null @@ -1,94 +0,0 @@ -using FluentAssertions; -using NUnit.Framework; -using NzbDrone.Core.Datastore; -using NzbDrone.Core.DecisionEngine.Specifications; -using NzbDrone.Core.Languages; -using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles.Languages; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Test.Languages; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Core.Test.DecisionEngineTests -{ - [TestFixture] - - public class LanguageSpecificationFixture : CoreTest - { - private RemoteEpisode _remoteEpisode; - - [SetUp] - public void Setup() - { - LanguageProfile profile = new LazyLoaded(new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(Language.English, Language.Spanish), - Cutoff = Language.Spanish - }); - - _remoteEpisode = new RemoteEpisode - { - ParsedEpisodeInfo = new ParsedEpisodeInfo - { - Language = Language.English - }, - Series = new Series - { - LanguageProfile = profile - } - }; - } - - private void WithEnglishRelease() - { - _remoteEpisode.ParsedEpisodeInfo.Language = Language.English; - } - - private void WithSpanishRelease() - { - _remoteEpisode.ParsedEpisodeInfo.Language = Language.Spanish; - } - - private void WithFrenchRelease() - { - _remoteEpisode.ParsedEpisodeInfo.Language = Language.French; - } - - private void WithGermanRelease() - { - _remoteEpisode.ParsedEpisodeInfo.Language = Language.German; - } - - [Test] - public void should_return_true_if_language_is_english() - { - WithEnglishRelease(); - - Mocker.Resolve().IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue(); - } - - [Test] - public void should_return_false_if_language_is_german() - { - WithGermanRelease(); - - Mocker.Resolve().IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse(); - } - - [Test] - public void should_return_false_if_language_is_french() - { - WithFrenchRelease(); - - Mocker.Resolve().IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse(); - } - - [Test] - public void should_return_true_if_language_is_spanish() - { - WithSpanishRelease(); - - Mocker.Resolve().IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue(); - } - } -} diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs index f7129eabf..47998ae21 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs @@ -12,7 +12,6 @@ using NzbDrone.Core.Languages; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Profiles.Delay; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; @@ -43,7 +42,7 @@ private RemoteEpisode GivenRemoteEpisode(List episodes, QualityModel qu var remoteEpisode = new RemoteEpisode(); remoteEpisode.ParsedEpisodeInfo = new ParsedEpisodeInfo(); remoteEpisode.ParsedEpisodeInfo.Quality = quality; - remoteEpisode.ParsedEpisodeInfo.Language = language; + remoteEpisode.ParsedEpisodeInfo.Languages = new List { language }; remoteEpisode.Episodes = new List(); remoteEpisode.Episodes.AddRange(episodes); @@ -59,11 +58,6 @@ private RemoteEpisode GivenRemoteEpisode(List episodes, QualityModel qu { Items = Qualities.QualityFixture.GetDefaultQualities() }) - .With(l => l.LanguageProfile = new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(), - Cutoff = Language.Spanish - }) .Build(); return remoteEpisode; @@ -434,23 +428,6 @@ public void should_prefer_quality_over_the_number_of_peers() ((TorrentInfo)qualifiedReports.First().RemoteEpisode.Release).Should().Be(torrentInfo1); } - [Test] - public void should_order_by_language() - { - var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English); - var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.French); - var remoteEpisode3 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.German); - - var decisions = new List(); - decisions.Add(new DownloadDecision(remoteEpisode1)); - decisions.Add(new DownloadDecision(remoteEpisode2)); - decisions.Add(new DownloadDecision(remoteEpisode3)); - - var qualifiedReports = Subject.PrioritizeDecisions(decisions); - qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.Language.Should().Be(Language.French); - qualifiedReports.Last().RemoteEpisode.ParsedEpisodeInfo.Language.Should().Be(Language.German); - } - [Test] public void should_put_higher_quality_before_lower_always() { diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/QueueSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/QueueSpecificationFixture.cs index b00ed8026..16dd56be4 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/QueueSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/QueueSpecificationFixture.cs @@ -10,7 +10,6 @@ using NzbDrone.Core.Languages; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Queue; @@ -47,12 +46,6 @@ public void Setup() FormatItems = CustomFormatsFixture.GetSampleFormatItems(), MinFormatScore = 0 }) - .With(l => l.LanguageProfile = new LanguageProfile - { - Languages = Languages.LanguageFixture.GetDefaultLanguages(), - UpgradeAllowed = true, - Cutoff = Language.Spanish - }) .Build(); _episode = Builder.CreateNew() @@ -76,7 +69,7 @@ public void Setup() _remoteEpisode = Builder.CreateNew() .With(r => r.Series = _series) .With(r => r.Episodes = new List { _episode }) - .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD), Language = Language.Spanish }) + .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD), Languages = new List { Language.Spanish } }) .With(r => r.CustomFormats = new List()) .Build(); @@ -144,7 +137,7 @@ public void should_return_false_if_everything_is_the_same() .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD), - Language = Language.Spanish + Languages = new List { Language.Spanish } }) .With(r => r.CustomFormats = new List()) .With(r => r.Release = _releaseInfo) @@ -159,7 +152,6 @@ public void should_return_false_if_everything_is_the_same() public void should_return_true_when_quality_in_queue_is_lower() { _series.QualityProfile.Value.Cutoff = Quality.Bluray1080p.Id; - _series.LanguageProfile.Value.Cutoff = Language.Spanish; var remoteEpisode = Builder.CreateNew() .With(r => r.Series = _series) @@ -167,7 +159,7 @@ public void should_return_true_when_quality_in_queue_is_lower() .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.SDTV), - Language = Language.Spanish + Languages = new List { Language.Spanish } }) .With(r => r.Release = _releaseInfo) .With(r => r.CustomFormats = new List()) @@ -181,7 +173,6 @@ public void should_return_true_when_quality_in_queue_is_lower() public void should_return_true_when_quality_in_queue_is_lower_but_language_is_higher() { _series.QualityProfile.Value.Cutoff = Quality.Bluray1080p.Id; - _series.LanguageProfile.Value.Cutoff = Language.Spanish; var remoteEpisode = Builder.CreateNew() .With(r => r.Series = _series) @@ -189,7 +180,7 @@ public void should_return_true_when_quality_in_queue_is_lower_but_language_is_hi .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.SDTV), - Language = Language.English + Languages = new List { Language.English } }) .With(r => r.Release = _releaseInfo) .With(r => r.CustomFormats = new List()) @@ -236,7 +227,7 @@ public void should_return_true_when_qualities_are_the_same_and_languages_are_the .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD), - Language = Language.Spanish, + Languages = new List { Language.Spanish }, }) .With(r => r.Release = _releaseInfo) .With(r => r.CustomFormats = lowFormat) @@ -255,7 +246,7 @@ public void should_return_false_when_qualities_are_the_same_and_languages_are_th .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD), - Language = Language.Spanish, + Languages = new List { Language.Spanish }, }) .With(r => r.Release = _releaseInfo) .With(r => r.CustomFormats = new List()) @@ -265,47 +256,6 @@ public void should_return_false_when_qualities_are_the_same_and_languages_are_th Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse(); } - [Test] - public void should_return_true_when_qualities_are_the_same_but_language_is_better() - { - var remoteEpisode = Builder.CreateNew() - .With(r => r.Series = _series) - .With(r => r.Episodes = new List { _episode }) - .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo - { - Quality = new QualityModel(Quality.DVD), - Language = Language.English, - }) - .With(r => r.Release = _releaseInfo) - .With(r => r.CustomFormats = new List()) - .Build(); - - GivenQueue(new List { remoteEpisode }); - Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue(); - } - - [Test] - public void should_return_true_when_quality_is_better_language_is_better_and_upgrade_allowed_is_false_for_quality_profile() - { - _series.QualityProfile.Value.Cutoff = Quality.Bluray1080p.Id; - _series.QualityProfile.Value.UpgradeAllowed = false; - - var remoteEpisode = Builder.CreateNew() - .With(r => r.Series = _series) - .With(r => r.Episodes = new List { _episode }) - .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo - { - Quality = new QualityModel(Quality.SDTV), - Language = Language.English - }) - .With(r => r.Release = _releaseInfo) - .With(r => r.CustomFormats = new List()) - .Build(); - - GivenQueue(new List { remoteEpisode }); - Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue(); - } - [Test] public void should_return_false_when_quality_in_queue_is_better() { @@ -317,7 +267,7 @@ public void should_return_false_when_quality_in_queue_is_better() .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.HDTV720p), - Language = Language.English + Languages = new List { Language.English } }) .With(r => r.Release = _releaseInfo) .With(r => r.CustomFormats = new List()) @@ -336,7 +286,7 @@ public void should_return_false_if_matching_multi_episode_is_in_queue() .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.HDTV720p), - Language = Language.English + Languages = new List { Language.English } }) .With(r => r.Release = _releaseInfo) .With(r => r.CustomFormats = new List()) @@ -355,7 +305,7 @@ public void should_return_false_if_multi_episode_has_one_episode_in_queue() .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.HDTV720p), - Language = Language.English + Languages = new List { Language.English } }) .With(r => r.Release = _releaseInfo) .With(r => r.CustomFormats = new List()) @@ -376,7 +326,7 @@ public void should_return_false_if_multi_part_episode_is_already_in_queue() .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.HDTV720p), - Language = Language.English + Languages = new List { Language.English } }) .With(r => r.Release = _releaseInfo) .With(r => r.CustomFormats = new List()) @@ -400,7 +350,7 @@ public void should_return_false_if_multi_part_episode_has_two_episodes_in_queue( Quality = new QualityModel( Quality.HDTV720p), - Language = Language.English + Languages = new List { Language.English } }) .With(r => r.Release = _releaseInfo) .TheFirst(1) @@ -425,7 +375,7 @@ public void should_return_false_if_quality_and_language_in_queue_meets_cutoff() .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.HDTV720p), - Language = Language.Spanish + Languages = new List { Language.Spanish } }) .With(r => r.Release = _releaseInfo) .With(r => r.CustomFormats = new List()) @@ -439,15 +389,13 @@ public void should_return_false_if_quality_and_language_in_queue_meets_cutoff() [Test] public void should_return_false_when_quality_are_the_same_language_is_better_and_upgrade_allowed_is_false_for_language_profile() { - _series.LanguageProfile.Value.UpgradeAllowed = false; - var remoteEpisode = Builder.CreateNew() .With(r => r.Series = _series) .With(r => r.Episodes = new List { _episode }) .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD), - Language = Language.English + Languages = new List { Language.English } }) .With(r => r.Release = _releaseInfo) .With(r => r.CustomFormats = new List()) @@ -469,7 +417,7 @@ public void should_return_false_when_quality_is_better_languages_are_the_same_an .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.Bluray1080p), - Language = Language.Spanish + Languages = new List { Language.Spanish } }) .With(r => r.Release = _releaseInfo) .With(r => r.CustomFormats = new List()) @@ -490,7 +438,7 @@ public void should_return_true_if_everything_is_the_same_for_failed_pending() .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD), - Language = Language.Spanish + Languages = new List { Language.Spanish } }) .With(r => r.Release = _releaseInfo) .With(r => r.CustomFormats = new List()) diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs index c75c301a9..11a6c3bbb 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs @@ -15,7 +15,6 @@ using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Profiles.Delay; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; @@ -27,7 +26,6 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync public class DelaySpecificationFixture : CoreTest { private QualityProfile _profile; - private LanguageProfile _langProfile; private DelayProfile _delayProfile; private RemoteEpisode _remoteEpisode; @@ -37,16 +35,12 @@ public void Setup() _profile = Builder.CreateNew() .Build(); - _langProfile = Builder.CreateNew() - .Build(); - _delayProfile = Builder.CreateNew() .With(d => d.PreferredProtocol = DownloadProtocol.Usenet) .Build(); var series = Builder.CreateNew() .With(s => s.QualityProfile = _profile) - .With(s => s.LanguageProfile = _langProfile) .Build(); _remoteEpisode = Builder.CreateNew() @@ -60,9 +54,6 @@ public void Setup() _profile.Cutoff = Quality.WEBDL720p.Id; - _langProfile.Cutoff = Language.Spanish; - _langProfile.Languages = Languages.LanguageFixture.GetDefaultLanguages(); - _remoteEpisode.ParsedEpisodeInfo = new ParsedEpisodeInfo(); _remoteEpisode.Release = new ReleaseInfo(); _remoteEpisode.Release.DownloadProtocol = DownloadProtocol.Usenet; @@ -86,7 +77,7 @@ private void GivenExistingFile(QualityModel quality, Language language) _remoteEpisode.Episodes.First().EpisodeFile = new EpisodeFile { Quality = quality, - Language = language, + Languages = new List { language }, SceneName = "Series.Title.S01E01.720p.HDTV.x264-Sonarr" }; } @@ -94,7 +85,7 @@ private void GivenExistingFile(QualityModel quality, Language language) private void GivenUpgradeForExistingFile() { Mocker.GetMock() - .Setup(s => s.IsUpgradable(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny>())) + .Setup(s => s.IsUpgradable(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny>())) .Returns(true); } @@ -127,7 +118,7 @@ public void should_be_true_when_profile_does_not_have_a_delay() public void should_be_false_when_quality_and_language_is_last_allowed_in_profile_and_bypass_disabled() { _remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.Bluray720p); - _remoteEpisode.ParsedEpisodeInfo.Language = Language.French; + _remoteEpisode.ParsedEpisodeInfo.Languages = new List { Language.French }; Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue(); } @@ -138,7 +129,7 @@ public void should_be_true_when_quality_and_language_is_last_allowed_in_profile_ _delayProfile.BypassIfHighestQuality = true; _remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.Bluray720p); - _remoteEpisode.ParsedEpisodeInfo.Language = Language.French; + _remoteEpisode.ParsedEpisodeInfo.Languages = new List { Language.French }; Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue(); } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/HistorySpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/HistorySpecificationFixture.cs index 7a78f9e6d..054da3649 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/HistorySpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/HistorySpecificationFixture.cs @@ -12,7 +12,6 @@ using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Languages; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.CustomFormats; @@ -61,18 +60,12 @@ public void Setup() MinFormatScore = 0, Items = Qualities.QualityFixture.GetDefaultQualities() }) - .With(l => l.LanguageProfile = new LanguageProfile - { - UpgradeAllowed = true, - Cutoff = Language.Spanish, - Languages = LanguageFixture.GetDefaultLanguages() - }) .Build(); _parseResultMulti = new RemoteEpisode { Series = _fakeSeries, - ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)), Language = Language.English }, + ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)), Languages = new List { Language.English } }, Episodes = doubleEpisodeList, CustomFormats = new List() }; @@ -80,7 +73,7 @@ public void Setup() _parseResultSingle = new RemoteEpisode { Series = _fakeSeries, - ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)), Language = Language.English }, + ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)), Languages = new List { Language.English } }, Episodes = singleEpisodeList, CustomFormats = new List() }; @@ -101,7 +94,7 @@ public void Setup() private void GivenMostRecentForEpisode(int episodeId, string downloadId, Tuple quality, DateTime date, EpisodeHistoryEventType eventType) { Mocker.GetMock().Setup(s => s.MostRecentForEpisode(episodeId)) - .Returns(new EpisodeHistory { DownloadId = downloadId, Quality = quality.Item1, Date = date, EventType = eventType, Language = quality.Item2 }); + .Returns(new EpisodeHistory { DownloadId = downloadId, Quality = quality.Item1, Date = date, EventType = eventType, Languages = new List { quality.Item2 } }); } private void GivenCdhDisabled() @@ -196,19 +189,6 @@ public void should_not_be_upgradable_if_episode_is_of_same_quality_as_existing() _upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); } - [Test] - public void should_be_upgradable_if_episode_is_of_same_quality_as_existing_but_new_has_better_language() - { - _fakeSeries.QualityProfile = new QualityProfile { Cutoff = Quality.WEBDL1080p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() }; - _parseResultSingle.ParsedEpisodeInfo.Quality = new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)); - _parseResultSingle.ParsedEpisodeInfo.Language = Language.Spanish; - _upgradableQuality = new Tuple(new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)), Language.English); - - GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, EpisodeHistoryEventType.Grabbed); - - _upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeTrue(); - } - [Test] public void should_not_be_upgradable_if_cutoff_already_met() { diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeAllowedSpecificationFixture .cs b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeAllowedSpecificationFixture .cs index aa98be479..315636e32 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeAllowedSpecificationFixture .cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeAllowedSpecificationFixture .cs @@ -2,7 +2,6 @@ using NUnit.Framework; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Languages; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; @@ -13,29 +12,6 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [TestFixture] public class UpgradeAllowedSpecificationFixture : CoreTest { - [Test] - public void should_return_false_when_quality_are_the_same_language_is_better_and_upgrade_allowed_is_false_for_language_profile() - { - Subject.IsUpgradeAllowed( - new QualityProfile - { - Cutoff = Quality.Bluray1080p.Id, - Items = Qualities.QualityFixture.GetDefaultQualities(), - UpgradeAllowed = true - }, - new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(Language.English, Language.French), - Cutoff = Language.French, - UpgradeAllowed = false - }, - new QualityModel(Quality.DVD), - Language.English, - new QualityModel(Quality.DVD), - Language.French) - .Should().BeFalse(); - } - [Test] public void should_return_false_when_quality_is_better_languages_are_the_same_and_upgrade_allowed_is_false_for_quality_profile() { @@ -46,16 +22,8 @@ public void should_return_false_when_quality_is_better_languages_are_the_same_an Items = Qualities.QualityFixture.GetDefaultQualities(), UpgradeAllowed = false }, - new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(Language.English), - Cutoff = Language.English, - UpgradeAllowed = true - }, new QualityModel(Quality.DVD), - Language.English, - new QualityModel(Quality.Bluray1080p), - Language.English) + new QualityModel(Quality.Bluray1080p)) .Should().BeFalse(); } @@ -69,16 +37,8 @@ public void should_return_true_for_language_upgrade_when_upgrading_is_allowed() Items = Qualities.QualityFixture.GetDefaultQualities(), UpgradeAllowed = true }, - new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(Language.English, Language.French), - Cutoff = Language.French, - UpgradeAllowed = true - }, new QualityModel(Quality.DVD), - Language.English, - new QualityModel(Quality.DVD), - Language.French) + new QualityModel(Quality.DVD)) .Should().BeTrue(); } @@ -92,16 +52,8 @@ public void should_return_true_for_same_language_when_upgrading_is_allowed() Items = Qualities.QualityFixture.GetDefaultQualities(), UpgradeAllowed = true }, - new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(Language.English, Language.French), - Cutoff = Language.French, - UpgradeAllowed = true - }, new QualityModel(Quality.DVD), - Language.English, - new QualityModel(Quality.DVD), - Language.English) + new QualityModel(Quality.DVD)) .Should().BeTrue(); } @@ -115,16 +67,8 @@ public void should_return_true_for_same_language_when_upgrading_is_not_allowed() Items = Qualities.QualityFixture.GetDefaultQualities(), UpgradeAllowed = true }, - new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(Language.English, Language.French), - Cutoff = Language.French, - UpgradeAllowed = false - }, new QualityModel(Quality.DVD), - Language.English, - new QualityModel(Quality.DVD), - Language.English) + new QualityModel(Quality.DVD)) .Should().BeTrue(); } @@ -138,16 +82,8 @@ public void should_return_true_for_lower_language_when_upgrading_is_allowed() Items = Qualities.QualityFixture.GetDefaultQualities(), UpgradeAllowed = true }, - new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(Language.English, Language.French), - Cutoff = Language.French, - UpgradeAllowed = true - }, new QualityModel(Quality.DVD), - Language.French, - new QualityModel(Quality.DVD), - Language.English) + new QualityModel(Quality.DVD)) .Should().BeTrue(); } @@ -161,16 +97,8 @@ public void should_return_true_for_lower_language_when_upgrading_is_not_allowed( Items = Qualities.QualityFixture.GetDefaultQualities(), UpgradeAllowed = true }, - new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(Language.English, Language.French), - Cutoff = Language.French, - UpgradeAllowed = false - }, new QualityModel(Quality.DVD), - Language.French, - new QualityModel(Quality.DVD), - Language.English) + new QualityModel(Quality.DVD)) .Should().BeTrue(); } @@ -184,16 +112,8 @@ public void should_return_true_for_quality_upgrade_when_upgrading_is_allowed() Items = Qualities.QualityFixture.GetDefaultQualities(), UpgradeAllowed = true }, - new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(Language.English), - Cutoff = Language.English, - UpgradeAllowed = true - }, new QualityModel(Quality.DVD), - Language.English, - new QualityModel(Quality.Bluray1080p), - Language.English) + new QualityModel(Quality.Bluray1080p)) .Should().BeTrue(); } @@ -207,16 +127,8 @@ public void should_return_true_for_same_quality_when_upgrading_is_allowed() Items = Qualities.QualityFixture.GetDefaultQualities(), UpgradeAllowed = true }, - new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(Language.English), - Cutoff = Language.English, - UpgradeAllowed = true - }, new QualityModel(Quality.DVD), - Language.English, - new QualityModel(Quality.DVD), - Language.English) + new QualityModel(Quality.DVD)) .Should().BeTrue(); } @@ -230,16 +142,8 @@ public void should_return_true_for_same_quality_when_upgrading_is_not_allowed() Items = Qualities.QualityFixture.GetDefaultQualities(), UpgradeAllowed = false }, - new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(Language.English), - Cutoff = Language.English, - UpgradeAllowed = true - }, new QualityModel(Quality.DVD), - Language.English, - new QualityModel(Quality.DVD), - Language.English) + new QualityModel(Quality.DVD)) .Should().BeTrue(); } @@ -253,16 +157,8 @@ public void should_return_true_for_lower_quality_when_upgrading_is_allowed() Items = Qualities.QualityFixture.GetDefaultQualities(), UpgradeAllowed = true }, - new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(Language.English), - Cutoff = Language.English, - UpgradeAllowed = true - }, new QualityModel(Quality.DVD), - Language.English, - new QualityModel(Quality.SDTV), - Language.English) + new QualityModel(Quality.SDTV)) .Should().BeTrue(); } @@ -276,16 +172,8 @@ public void should_return_true_for_lower_quality_when_upgrading_is_not_allowed() Items = Qualities.QualityFixture.GetDefaultQualities(), UpgradeAllowed = false }, - new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(Language.English), - Cutoff = Language.English, - UpgradeAllowed = true - }, new QualityModel(Quality.DVD), - Language.English, - new QualityModel(Quality.SDTV), - Language.English) + new QualityModel(Quality.SDTV)) .Should().BeTrue(); } } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs index 5e52407da..f75915b69 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs @@ -10,7 +10,6 @@ using NzbDrone.Core.Languages; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.CustomFormats; @@ -38,14 +37,12 @@ public void Setup() CustomFormatsFixture.GivenCustomFormats(); - _firstFile = new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 2)), DateAdded = DateTime.Now, Language = Language.English }; - _secondFile = new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 2)), DateAdded = DateTime.Now, Language = Language.English }; + _firstFile = new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 2)), DateAdded = DateTime.Now, Languages = new List { Language.English } }; + _secondFile = new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 2)), DateAdded = DateTime.Now, Languages = new List { Language.English } }; var singleEpisodeList = new List { new Episode { EpisodeFile = _firstFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = null } }; var doubleEpisodeList = new List { new Episode { EpisodeFile = _firstFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = _secondFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = null } }; - var languages = Languages.LanguageFixture.GetDefaultLanguages(Language.English, Language.Spanish); - var fakeSeries = Builder.CreateNew() .With(c => c.QualityProfile = new QualityProfile { @@ -55,18 +52,12 @@ public void Setup() FormatItems = CustomFormatsFixture.GetSampleFormatItems("None"), MinFormatScore = 0, }) - .With(l => l.LanguageProfile = new LanguageProfile - { - UpgradeAllowed = true, - Cutoff = Language.Spanish, - Languages = languages - }) .Build(); _parseResultMulti = new RemoteEpisode { Series = fakeSeries, - ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)), Language = Language.English }, + ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)), Languages = new List { Language.English } }, Episodes = doubleEpisodeList, CustomFormats = new List() }; @@ -74,7 +65,7 @@ public void Setup() _parseResultSingle = new RemoteEpisode { Series = fakeSeries, - ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)), Language = Language.English }, + ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)), Languages = new List { Language.English } }, Episodes = singleEpisodeList, CustomFormats = new List() }; diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeSpecificationFixture.cs index d79bfe5c3..579cbc2b3 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeSpecificationFixture.cs @@ -5,7 +5,6 @@ using NzbDrone.Core.CustomFormats; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Languages; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; @@ -57,53 +56,11 @@ public void IsUpgradeTest(Quality current, int currentVersion, Quality newQualit Items = Qualities.QualityFixture.GetDefaultQualities() }; - var langProfile = new LanguageProfile - { - UpgradeAllowed = true, - Languages = LanguageFixture.GetDefaultLanguages(), - Cutoff = Language.English - }; - Subject.IsUpgradable( profile, - langProfile, new QualityModel(current, new Revision(version: currentVersion)), - Language.English, new List(), new QualityModel(newQuality, new Revision(version: newVersion)), - Language.English, - new List()) - .Should().Be(expected); - } - - [Test] - [TestCaseSource("IsUpgradeTestCasesLanguages")] - public void IsUpgradeTestLanguage(Quality current, int currentVersion, Language currentLanguage, Quality newQuality, int newVersion, Language newLanguage, Quality cutoff, Language languageCutoff, bool expected) - { - GivenAutoDownloadPropers(ProperDownloadTypes.PreferAndUpgrade); - - var profile = new QualityProfile - { - UpgradeAllowed = true, - Items = Qualities.QualityFixture.GetDefaultQualities(), - Cutoff = cutoff.Id, - }; - - var langProfile = new LanguageProfile - { - UpgradeAllowed = true, - Languages = LanguageFixture.GetDefaultLanguages(), - Cutoff = languageCutoff - }; - - Subject.IsUpgradable( - profile, - langProfile, - new QualityModel(current, new Revision(version: currentVersion)), - currentLanguage, - new List(), - new QualityModel(newQuality, new Revision(version: newVersion)), - newLanguage, new List()) .Should().Be(expected); } @@ -118,20 +75,11 @@ public void should_return_true_if_proper_and_download_propers_is_do_not_download Items = Qualities.QualityFixture.GetDefaultQualities(), }; - var langProfile = new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(), - Cutoff = Language.English - }; - Subject.IsUpgradable( profile, - langProfile, new QualityModel(Quality.DVD, new Revision(version: 1)), - Language.English, new List(), new QualityModel(Quality.DVD, new Revision(version: 2)), - Language.English, new List()) .Should().BeTrue(); } @@ -146,20 +94,11 @@ public void should_return_false_if_proper_and_autoDownloadPropers_is_do_not_pref Items = Qualities.QualityFixture.GetDefaultQualities(), }; - var langProfile = new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(), - Cutoff = Language.English - }; - Subject.IsUpgradable( profile, - langProfile, new QualityModel(Quality.DVD, new Revision(version: 1)), - Language.English, new List(), new QualityModel(Quality.DVD, new Revision(version: 2)), - Language.English, new List()) .Should().BeFalse(); } @@ -172,20 +111,11 @@ public void should_return_false_if_release_and_existing_file_are_the_same() Items = Qualities.QualityFixture.GetDefaultQualities(), }; - var langProfile = new LanguageProfile - { - Languages = LanguageFixture.GetDefaultLanguages(), - Cutoff = Language.English - }; - Subject.IsUpgradable( profile, - langProfile, new QualityModel(Quality.HDTV720p, new Revision(version: 1)), - Language.English, new List(), new QualityModel(Quality.HDTV720p, new Revision(version: 1)), - Language.English, new List()) .Should().BeFalse(); } diff --git a/src/NzbDrone.Core.Test/HistoryTests/HistoryRepositoryFixture.cs b/src/NzbDrone.Core.Test/HistoryTests/HistoryRepositoryFixture.cs index 4e6815243..79bbfc323 100644 --- a/src/NzbDrone.Core.Test/HistoryTests/HistoryRepositoryFixture.cs +++ b/src/NzbDrone.Core.Test/HistoryTests/HistoryRepositoryFixture.cs @@ -1,4 +1,5 @@ -using FizzWare.NBuilder; +using System.Collections.Generic; +using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.History; @@ -15,7 +16,7 @@ public class HistoryRepositoryFixture : DbTest.CreateNew() - .With(c => c.Language = Language.English) + .With(c => c.Languages = new List { Language.English }) .With(c => c.Quality = new QualityModel()) .BuildNew(); @@ -31,14 +32,14 @@ public void should_read_write_dictionary() public void should_get_download_history() { var historyBluray = Builder.CreateNew() - .With(c => c.Language = Language.English) + .With(c => c.Languages = new List { Language.English }) .With(c => c.Quality = new QualityModel(Quality.Bluray1080p)) .With(c => c.SeriesId = 12) .With(c => c.EventType = EpisodeHistoryEventType.Grabbed) .BuildNew(); var historyDvd = Builder.CreateNew() - .With(c => c.Language = Language.English) + .With(c => c.Languages = new List { Language.English }) .With(c => c.Quality = new QualityModel(Quality.DVD)) .With(c => c.SeriesId = 12) .With(c => c.EventType = EpisodeHistoryEventType.Grabbed) diff --git a/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs b/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs index e73cb901d..e74861f2f 100644 --- a/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs +++ b/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using FizzWare.NBuilder; @@ -11,7 +11,6 @@ using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; @@ -24,7 +23,6 @@ public class HistoryServiceFixture : CoreTest { private QualityProfile _profile; private QualityProfile _profileCustom; - private LanguageProfile _languageProfile; [SetUp] public void Setup() @@ -40,12 +38,6 @@ public void Setup() Cutoff = Quality.WEBDL720p.Id, Items = QualityFixture.GetDefaultQualities(Quality.DVD), }; - - _languageProfile = new LanguageProfile - { - Cutoff = Language.Spanish, - Languages = Languages.LanguageFixture.GetDefaultLanguages() - }; } [Test] diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedBlocklistFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedBlocklistFixture.cs index a572bd2fb..c6318b57a 100644 --- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedBlocklistFixture.cs +++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedBlocklistFixture.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; @@ -18,7 +18,7 @@ public class CleanupOrphanedBlocklistFixture : DbTest.CreateNew() - .With(h => h.Language = Language.English) + .With(h => h.Languages = new List { Language.English }) .With(h => h.EpisodeIds = new List()) .With(h => h.Quality = new QualityModel()) .BuildNew(); @@ -36,7 +36,7 @@ public void should_not_delete_unorphaned_blocklist_items() Db.Insert(series); var blocklist = Builder.CreateNew() - .With(h => h.Language = Language.English) + .With(h => h.Languages = new List { Language.English }) .With(h => h.EpisodeIds = new List()) .With(h => h.Quality = new QualityModel()) .With(b => b.SeriesId = series.Id) diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedEpisodeFilesFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedEpisodeFilesFixture.cs index 9a23183c3..93ba6e339 100644 --- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedEpisodeFilesFixture.cs +++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedEpisodeFilesFixture.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; @@ -18,7 +19,7 @@ public class CleanupOrphanedEpisodeFilesFixture : DbTest.CreateNew() - .With(h => h.Language = Language.English) + .With(h => h.Languages = new List { Language.English }) .With(h => h.Quality = new QualityModel()) .BuildNew(); @@ -32,7 +33,7 @@ public void should_not_delete_unorphaned_episode_files() { var episodeFiles = Builder.CreateListOfSize(2) .All() - .With(h => h.Language = Language.English) + .With(h => h.Languages = new List { Language.English }) .With(h => h.Quality = new QualityModel()) .BuildListOfNew(); diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedHistoryItemsFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedHistoryItemsFixture.cs index ee93c41b1..92035eb9d 100644 --- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedHistoryItemsFixture.cs +++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedHistoryItemsFixture.cs @@ -1,4 +1,5 @@ -using FizzWare.NBuilder; +using System.Collections.Generic; +using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.History; @@ -42,7 +43,7 @@ public void should_delete_orphaned_items_by_series() GivenEpisode(); var history = Builder.CreateNew() - .With(h => h.Language = Language.English) + .With(h => h.Languages = new List { Language.English }) .With(h => h.Quality = new QualityModel()) .With(h => h.EpisodeId = _episode.Id) .BuildNew(); @@ -58,7 +59,7 @@ public void should_delete_orphaned_items_by_episode() GivenSeries(); var history = Builder.CreateNew() - .With(h => h.Language = Language.English) + .With(h => h.Languages = new List { Language.English }) .With(h => h.Quality = new QualityModel()) .With(h => h.SeriesId = _series.Id) .BuildNew(); @@ -76,7 +77,7 @@ public void should_not_delete_unorphaned_data_by_series() var history = Builder.CreateListOfSize(2) .All() - .With(h => h.Language = Language.English) + .With(h => h.Languages = new List { Language.English }) .With(h => h.Quality = new QualityModel()) .With(h => h.EpisodeId = _episode.Id) .TheFirst(1) @@ -98,7 +99,7 @@ public void should_not_delete_unorphaned_data_by_episode() var history = Builder.CreateListOfSize(2) .All() - .With(h => h.Language = Language.English) + .With(h => h.Languages = new List { Language.English }) .With(h => h.Quality = new QualityModel()) .With(h => h.SeriesId = _series.Id) .TheFirst(1) diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedMetadataFilesFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedMetadataFilesFixture.cs index f776c98b5..80abd8eb5 100644 --- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedMetadataFilesFixture.cs +++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedMetadataFilesFixture.cs @@ -1,4 +1,5 @@ -using FizzWare.NBuilder; +using System.Collections.Generic; +using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Extras.Metadata; @@ -71,7 +72,7 @@ public void should_not_delete_metadata_files_that_have_a_coresponding_episode_fi var episodeFile = Builder.CreateNew() .With(h => h.Quality = new QualityModel()) - .With(h => h.Language = Language.English) + .With(h => h.Languages = new List { Language.English }) .BuildNew(); Db.Insert(series); diff --git a/src/NzbDrone.Core.Test/Languages/LanguageFixture.cs b/src/NzbDrone.Core.Test/Languages/LanguageFixture.cs index 90b61ef92..8a27cba4e 100644 --- a/src/NzbDrone.Core.Test/Languages/LanguageFixture.cs +++ b/src/NzbDrone.Core.Test/Languages/LanguageFixture.cs @@ -1,9 +1,8 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Languages; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.Languages @@ -78,27 +77,5 @@ public void should_be_able_to_convert_languageTypes_to_int(Language source, int var i = (int)source; i.Should().Be(expected); } - - public static List GetDefaultLanguages(params Language[] allowed) - { - var languages = new List - { - Language.English, - Language.Spanish, - Language.French - }; - - if (allowed.Length == 0) - { - allowed = languages.ToArray(); - } - - var items = languages - .Except(allowed) - .Concat(allowed) - .Select(v => new LanguageProfileItem { Language = v, Allowed = allowed.Contains(v) }).ToList(); - - return items; - } } } diff --git a/src/NzbDrone.Core.Test/Languages/LanguageProfileRepositoryFixture.cs b/src/NzbDrone.Core.Test/Languages/LanguageProfileRepositoryFixture.cs deleted file mode 100644 index cff03b447..000000000 --- a/src/NzbDrone.Core.Test/Languages/LanguageProfileRepositoryFixture.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Linq; -using FluentAssertions; -using NUnit.Framework; -using NzbDrone.Core.Languages; -using NzbDrone.Core.Profiles.Languages; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.Languages -{ - [TestFixture] - public class LanguageProfileRepositoryFixture : DbTest - { - [Test] - public void should_be_able_to_read_and_write() - { - var profile = new LanguageProfile - { - Languages = Language.All.OrderByDescending(l => l.Name).Select(l => new LanguageProfileItem { Language = l, Allowed = l == Language.English }).ToList(), - Name = "TestProfile", - Cutoff = Language.English - }; - - Subject.Insert(profile); - - StoredModel.Name.Should().Be(profile.Name); - StoredModel.Cutoff.Should().Be(profile.Cutoff); - - StoredModel.Languages.Should().Equal(profile.Languages, (a, b) => a.Language == b.Language && a.Allowed == b.Allowed); - } - } -} diff --git a/src/NzbDrone.Core.Test/Languages/LanguageProfileServiceFixture.cs b/src/NzbDrone.Core.Test/Languages/LanguageProfileServiceFixture.cs deleted file mode 100644 index 0d63f98da..000000000 --- a/src/NzbDrone.Core.Test/Languages/LanguageProfileServiceFixture.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using FizzWare.NBuilder; -using Moq; -using NUnit.Framework; -using NzbDrone.Core.ImportLists; -using NzbDrone.Core.Lifecycle; -using NzbDrone.Core.Profiles.Languages; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Core.Test.Languages -{ - [TestFixture] - - public class LanguageProfileServiceFixture : CoreTest - { - [SetUp] - public void Setup() - { - Mocker.GetMock() - .Setup(s => s.All()) - .Returns(new List()); - } - - [Test] - public void init_should_add_default_profiles() - { - Subject.Handle(new ApplicationStartedEvent()); - - Mocker.GetMock() - .Verify(v => v.Insert(It.IsAny()), Times.Once()); - } - - [Test] - - //This confirms that new profiles are added only if no other profiles exists. - //We don't want to keep adding them back if a user deleted them on purpose. - public void Init_should_skip_if_any_profiles_already_exist() - { - Mocker.GetMock() - .Setup(s => s.All()) - .Returns(Builder.CreateListOfSize(2).Build().ToList()); - - Subject.Handle(new ApplicationStartedEvent()); - - Mocker.GetMock() - .Verify(v => v.Insert(It.IsAny()), Times.Never()); - } - - [Test] - public void should_not_be_able_to_delete_profile_if_assigned_to_series() - { - var seriesList = Builder.CreateListOfSize(3) - .Random(1) - .With(c => c.LanguageProfileId = 2) - .Build().ToList(); - - Mocker.GetMock().Setup(c => c.GetAllSeries()).Returns(seriesList); - - Assert.Throws(() => Subject.Delete(2)); - - Mocker.GetMock().Verify(c => c.Delete(It.IsAny()), Times.Never()); - } - - [Test] - public void should_delete_profile_if_not_assigned_to_series() - { - var seriesList = Builder.CreateListOfSize(3) - .All() - .With(c => c.LanguageProfileId = 2) - .Build().ToList(); - - Mocker.GetMock().Setup(c => c.GetAllSeries()).Returns(seriesList); - - Subject.Delete(1); - - Mocker.GetMock().Verify(c => c.Delete(1), Times.Once()); - } - - [Test] - public void should_not_be_able_to_delete_profile_if_assigned_to_import_list() - { - var seriesList = Builder.CreateListOfSize(3) - .Random(1) - .With(c => c.LanguageProfileId = 2) - .Build().ToList(); - - var importLists = Builder.CreateListOfSize(3) - .Random(1) - .With(c => c.LanguageProfileId = 1) - .Build().ToList(); - - Mocker.GetMock().Setup(c => c.GetAllSeries()).Returns(seriesList); - - Mocker.GetMock() - .Setup(s => s.All()) - .Returns(importLists); - - Assert.Throws(() => Subject.Delete(1)); - - Mocker.GetMock().Verify(c => c.Delete(It.IsAny()), Times.Never()); - } - } -} diff --git a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateLanguageFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateLanguageFixture.cs index 72a35f8e5..833b76b42 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateLanguageFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateLanguageFixture.cs @@ -1,9 +1,13 @@ +using System.Collections.Generic; using System.Linq; using FizzWare.NBuilder; using FluentAssertions; +using Moq; using NUnit.Framework; +using NzbDrone.Core.Download; using NzbDrone.Core.Languages; using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators; +using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators.Augmenters.Language; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; @@ -30,68 +34,152 @@ public void Setup() .Build(); } - private ParsedEpisodeInfo GetParsedEpisodeInfo(Language language, string releaseTitle) + private void GivenAugmenters(List fileNameLanguages, List folderNameLanguages, List clientLanguages, List mediaInfoLanguages) + { + var fileNameAugmenter = new Mock(); + var folderNameAugmenter = new Mock(); + var clientInfoAugmenter = new Mock(); + var mediaInfoAugmenter = new Mock(); + + fileNameAugmenter.Setup(s => s.AugmentLanguage(It.IsAny(), It.IsAny())) + .Returns(new AugmentLanguageResult(fileNameLanguages, Confidence.Filename)); + + folderNameAugmenter.Setup(s => s.AugmentLanguage(It.IsAny(), It.IsAny())) + .Returns(new AugmentLanguageResult(folderNameLanguages, Confidence.Foldername)); + + clientInfoAugmenter.Setup(s => s.AugmentLanguage(It.IsAny(), It.IsAny())) + .Returns(new AugmentLanguageResult(clientLanguages, Confidence.DownloadClientItem)); + + mediaInfoAugmenter.Setup(s => s.AugmentLanguage(It.IsAny(), It.IsAny())) + .Returns(new AugmentLanguageResult(mediaInfoLanguages, Confidence.MediaInfo)); + + var mocks = new List> { fileNameAugmenter, folderNameAugmenter, clientInfoAugmenter, mediaInfoAugmenter }; + + Mocker.SetConstant>(mocks.Select(c => c.Object)); + } + + private ParsedEpisodeInfo GetParsedEpisodeInfo(List languages, string releaseTitle) { return new ParsedEpisodeInfo { - Language = language, + Languages = languages, ReleaseTitle = releaseTitle }; } + [Test] + public void should_return_default_if_no_info_is_known() + { + var result = Subject.Aggregate(_localEpisode, null); + + result.Languages.Should().Contain(Language.English); + } + [Test] public void should_return_file_language_when_only_file_info_is_known() { - _localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(Language.English, _simpleReleaseTitle); + _localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(new List { Language.French }, _simpleReleaseTitle); - Subject.Aggregate(_localEpisode, null).Language.Should().Be(_localEpisode.FileEpisodeInfo.Language); + GivenAugmenters(new List { Language.French }, + null, + null, + null); + + Subject.Aggregate(_localEpisode, null).Languages.Should().Equal(_localEpisode.FileEpisodeInfo.Languages); } [Test] public void should_return_folder_language_when_folder_info_is_known() { - _localEpisode.FolderEpisodeInfo = GetParsedEpisodeInfo(Language.English, _simpleReleaseTitle); - _localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(Language.English, _simpleReleaseTitle); + _localEpisode.FolderEpisodeInfo = GetParsedEpisodeInfo(new List { Language.German }, _simpleReleaseTitle); + _localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(new List { Language.French }, _simpleReleaseTitle); - Subject.Aggregate(_localEpisode, null).Language.Should().Be(_localEpisode.FolderEpisodeInfo.Language); + GivenAugmenters(new List { Language.French }, + new List { Language.German }, + null, + null); + + Subject.Aggregate(_localEpisode, null).Languages.Should().Equal(_localEpisode.FolderEpisodeInfo.Languages); } [Test] public void should_return_download_client_item_language_when_download_client_item_info_is_known() { - _localEpisode.DownloadClientEpisodeInfo = GetParsedEpisodeInfo(Language.English, _simpleReleaseTitle); - _localEpisode.FolderEpisodeInfo = GetParsedEpisodeInfo(Language.English, _simpleReleaseTitle); - _localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(Language.English, _simpleReleaseTitle); + _localEpisode.DownloadClientEpisodeInfo = GetParsedEpisodeInfo(new List { Language.Spanish }, _simpleReleaseTitle); + _localEpisode.FolderEpisodeInfo = GetParsedEpisodeInfo(new List { Language.German }, _simpleReleaseTitle); + _localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(new List { Language.French }, _simpleReleaseTitle); - Subject.Aggregate(_localEpisode, null).Language.Should().Be(_localEpisode.DownloadClientEpisodeInfo.Language); + GivenAugmenters(new List { Language.French }, + new List { Language.German }, + new List { Language.Spanish }, + null); + + Subject.Aggregate(_localEpisode, null).Languages.Should().Equal(_localEpisode.DownloadClientEpisodeInfo.Languages); } [Test] public void should_return_file_language_when_file_language_is_higher_than_others() { - _localEpisode.DownloadClientEpisodeInfo = GetParsedEpisodeInfo(Language.English, _simpleReleaseTitle); - _localEpisode.FolderEpisodeInfo = GetParsedEpisodeInfo(Language.English, _simpleReleaseTitle); - _localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(Language.French, _simpleReleaseTitle); + _localEpisode.DownloadClientEpisodeInfo = GetParsedEpisodeInfo(new List { Language.English }, _simpleReleaseTitle); + _localEpisode.FolderEpisodeInfo = GetParsedEpisodeInfo(new List { Language.English }, _simpleReleaseTitle); + _localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(new List { Language.French }, _simpleReleaseTitle); - Subject.Aggregate(_localEpisode, null).Language.Should().Be(_localEpisode.FileEpisodeInfo.Language); + GivenAugmenters(new List { Language.French }, + new List { Language.English }, + new List { Language.English }, + null); + + Subject.Aggregate(_localEpisode, null).Languages.Should().Contain(_localEpisode.FileEpisodeInfo.Languages); + } + + [Test] + public void should_return_multi_language() + { + GivenAugmenters(new List { Language.English }, + new List { Language.French, Language.German }, + new List { Language.English }, + null); + + Subject.Aggregate(_localEpisode, null).Languages.Should().Equal(new List { Language.French, Language.German }); + } + + [Test] + public void should_use_mediainfo_over_others() + { + GivenAugmenters(new List { Language.Unknown }, + new List { Language.French, Language.German }, + new List { Language.Unknown }, + new List { Language.Japanese, Language.English }); + + Subject.Aggregate(_localEpisode, null).Languages.Should().Equal(new List { Language.Japanese, Language.English }); } [Test] public void should_return_english_if_parsed_language_is_in_episode_title_and_release_title_contains_episode_title() { _localEpisode.Episodes.First().Title = "The Swedish Job"; - _localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(Language.Swedish, "Series.Title.S01E01.The.Swedish.Job.720p.WEB-DL-RlsGrp"); + _localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(new List { Language.Swedish }, "Series.Title.S01E01.The.Swedish.Job.720p.WEB-DL-RlsGrp"); - Subject.Aggregate(_localEpisode, null).Language.Should().Be(Language.English); + GivenAugmenters(new List { }, + null, + null, + null); + + Subject.Aggregate(_localEpisode, null).Languages.Should().Contain(Language.English); } [Test] public void should_return_parsed_if_parsed_language_is_not_episode_title_and_release_title_contains_episode_title() { _localEpisode.Episodes.First().Title = "The Swedish Job"; - _localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(Language.French, "Series.Title.S01E01.The.Swedish.Job.720p.WEB-DL-RlsGrp"); + _localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(new List { Language.French }, "Series.Title.S01E01.The.Swedish.Job.720p.WEB-DL-RlsGrp"); - Subject.Aggregate(_localEpisode, null).Language.Should().Be(_localEpisode.FileEpisodeInfo.Language); + GivenAugmenters(new List { Language.French }, + null, + null, + null); + + Subject.Aggregate(_localEpisode, null).Languages.Should().Contain(_localEpisode.FileEpisodeInfo.Languages); } } } diff --git a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/GetSceneNameFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/GetSceneNameFixture.cs index 704218fbe..7b3001030 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/GetSceneNameFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/GetSceneNameFixture.cs @@ -8,7 +8,6 @@ using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; @@ -29,11 +28,6 @@ public void Setup() { var series = Builder.CreateNew() .With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() }) - .With(l => l.LanguageProfile = new LanguageProfile - { - Cutoff = Language.Spanish, - Languages = Languages.LanguageFixture.GetDefaultLanguages() - }) .With(s => s.Path = @"C:\Test\TV\Series Title".AsOsAgnostic()) .Build(); diff --git a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportApprovedEpisodesFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportApprovedEpisodesFixture.cs index 44d7cdfd0..910580fca 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportApprovedEpisodesFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportApprovedEpisodesFixture.cs @@ -14,7 +14,6 @@ using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; @@ -41,11 +40,6 @@ public void Setup() var series = Builder.CreateNew() .With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() }) - .With(l => l.LanguageProfile = new LanguageProfile - { - Cutoff = Language.Spanish, - Languages = Languages.LanguageFixture.GetDefaultLanguages() - }) .With(s => s.Path = @"C:\Test\TV\30 Rock".AsOsAgnostic()) .Build(); diff --git a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs index f613a0937..7fddbd1fe 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs @@ -11,7 +11,6 @@ using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; @@ -58,7 +57,6 @@ public void Setup() _series = Builder.CreateNew() .With(e => e.Path = @"C:\Test\Series".AsOsAgnostic()) .With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() }) - .With(e => e.LanguageProfile = new LanguageProfile { Languages = Languages.LanguageFixture.GetDefaultLanguages() }) .Build(); _quality = new QualityModel(Quality.DVD); @@ -67,7 +65,7 @@ public void Setup() { Series = _series, Quality = _quality, - Language = Language.Spanish, + Languages = new List { Language.Spanish }, Episodes = new List { new Episode() }, Path = @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.Spanish.XviD-OSiTV.avi" }; diff --git a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs index fbab5bcd5..1bc867448 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs @@ -12,7 +12,6 @@ using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Profiles.Releases; using NzbDrone.Core.Qualities; @@ -37,18 +36,13 @@ public void Setup() { Items = Qualities.QualityFixture.GetDefaultQualities(), }) - .With(l => l.LanguageProfile = new LanguageProfile - { - Languages = Languages.LanguageFixture.GetDefaultLanguages(), - Cutoff = Language.Spanish, - }) .Build(); _localEpisode = new LocalEpisode { Path = @"C:\Test\30 Rock\30.rock.s01e01.avi", Quality = new QualityModel(Quality.HDTV720p, new Revision(version: 1)), - Language = Language.Spanish, + Languages = new List { Language.Spanish }, Series = _series }; } @@ -89,7 +83,7 @@ public void should_return_true_if_upgrade_for_existing_episodeFile() new EpisodeFile { Quality = new QualityModel(Quality.SDTV, new Revision(version: 1)), - Language = Language.Spanish + Languages = new List { Language.Spanish } })) .Build() .ToList(); @@ -107,7 +101,7 @@ public void should_return_true_if_language_upgrade_for_existing_episodeFile_and_ new EpisodeFile { Quality = new QualityModel(Quality.HDTV720p, new Revision(version: 1)), - Language = Language.English + Languages = new List { Language.English } })) .Build() .ToList(); @@ -115,24 +109,6 @@ public void should_return_true_if_language_upgrade_for_existing_episodeFile_and_ Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue(); } - [Test] - public void should_return_true_if_language_upgrade_for_existing_episodeFile_and_quality_is_same_but_lower_revision() - { - _localEpisode.Episodes = Builder.CreateListOfSize(1) - .All() - .With(e => e.EpisodeFileId = 1) - .With(e => e.EpisodeFile = new LazyLoaded( - new EpisodeFile - { - Quality = new QualityModel(Quality.HDTV720p, new Revision(version: 2)), - Language = Language.English - })) - .Build() - .ToList(); - - Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue(); - } - [Test] public void should_return_false_if_language_upgrade_for_existing_episodeFile_and_quality_is_worse() { @@ -143,7 +119,7 @@ public void should_return_false_if_language_upgrade_for_existing_episodeFile_and new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 1)), - Language = Language.English + Languages = new List { Language.English } })) .Build() .ToList(); @@ -178,7 +154,7 @@ public void should_return_true_if_language_upgrade_for_existing_episodeFile_for_ new EpisodeFile { Quality = new QualityModel(Quality.HDTV720p, new Revision(version: 1)), - Language = Language.English + Languages = new List { Language.English } })) .Build() .ToList(); @@ -196,7 +172,7 @@ public void should_return_false_if_language_upgrade_for_existing_episodeFile_for new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 1)), - Language = Language.English + Languages = new List { Language.English } })) .Build() .ToList(); @@ -276,7 +252,7 @@ public void should_return_false_if_not_a_revision_upgrade_and_prefers_propers() new EpisodeFile { Quality = new QualityModel(Quality.HDTV720p, new Revision(version: 2)), - Language = Language.Spanish + Languages = new List { Language.Spanish } })) .Build() .ToList(); @@ -314,7 +290,7 @@ public void should_return_false_if_it_is_a_preferred_word_downgrade_and_equal_la new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p), - Language = Language.Spanish + Languages = new List { Language.Spanish } })) .Build() .ToList(); @@ -348,7 +324,7 @@ public void should_return_true_if_it_is_a_preferred_word_downgrade_and_language_ new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p), - Language = Language.French + Languages = new List { Language.French } })) .Build() .ToList(); @@ -382,7 +358,7 @@ public void should_return_true_if_it_is_a_preferred_word_downgrade_but_a_languag new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p), - Language = Language.English + Languages = new List { Language.English } })) .Build() .ToList(); diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaFileRepositoryFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaFileRepositoryFixture.cs index 0475f074e..9f7b33e6b 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/MediaFileRepositoryFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/MediaFileRepositoryFixture.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; @@ -17,7 +18,7 @@ public void get_files_by_series() var files = Builder.CreateListOfSize(10) .All() .With(c => c.Id = 0) - .With(c => c.Language = Language.English) + .With(c => c.Languages = new List { Language.English }) .With(c => c.Quality = new QualityModel(Quality.Bluray720p)) .Random(4) .With(s => s.SeriesId = 12) diff --git a/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs index 1462ec672..f4743754b 100644 --- a/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs @@ -1,3 +1,4 @@ +using System.Linq; using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Languages; @@ -23,8 +24,8 @@ public class LanguageParserFixture : CoreTest [TestCase("Title.the.Spanish.Series.S02E06.Field.of.Cloth.of.Gold.1080p.AMZN.WEBRip.DDP5.1.x264-NTb")] public void should_parse_language_english(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Should().Be(Language.English); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Should().Be(Language.English); } [TestCase("Series Title - S01E01 - Pilot.sub")] @@ -59,8 +60,8 @@ public void should_parse_subtitle_language_english(string fileName) [TestCase("Title.S01.720p.TRUEFRENCH.WEB-DL.AAC2.0.H.264-BTN")] public void should_parse_language_french(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.French.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.French.Id); } [TestCase("Title.the.Series.2009.S01E14.Spanish.HDTV.XviD-LOL")] @@ -68,8 +69,8 @@ public void should_parse_language_french(string postTitle) [TestCase("Series Title - Temporada 2 [HDTV 720p][Cap.206][AC3 5.1 Español Castellano]")] public void should_parse_language_spanish(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Spanish.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Spanish.Id); } [TestCase("Title.the.Series.2009.S01E14.German.HDTV.XviD-LOL")] @@ -78,45 +79,45 @@ public void should_parse_language_spanish(string postTitle) [TestCase("Series.Title.S01E03.Ger.Dub.AAC.1080p.WebDL.x264-TKP21")] public void should_parse_language_german(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.German.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.German.Id); } [TestCase("Title.the.Series.2009.S01E14.Italian.HDTV.XviD-LOL")] [TestCase("Title.the.Series.1x19.ita.720p.bdmux.x264-novarip")] public void should_parse_language_italian(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Italian.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Italian.Id); } [TestCase("Title.the.Series.2009.S01E14.Danish.HDTV.XviD-LOL")] public void should_parse_language_danish(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Danish.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Danish.Id); } [TestCase("Title.the.Series.2009.S01E14.Dutch.HDTV.XviD-LOL")] public void should_parse_language_dutch(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Dutch.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Dutch.Id); } [TestCase("Title.the.Series.2009.S01E14.Japanese.HDTV.XviD-LOL")] public void should_parse_language_japanese(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Japanese.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Japanese.Id); } [TestCase("Title.the.Series.2009.S01E14.Icelandic.HDTV.XviD-LOL")] [TestCase("Title.the.Series.S01E03.1080p.WEB-DL.DD5.1.H.264-SbR Icelandic")] public void should_parse_language_icelandic(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Icelandic.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Icelandic.Id); } [TestCase("Title.the.Series.2009.S01E14.Chinese.HDTV.XviD-LOL")] @@ -134,23 +135,23 @@ public void should_parse_language_icelandic(string postTitle) [TestCase("[喵萌奶茶屋&LoliHouse] 拳愿阿修罗 / Kengan Ashura - 17 [WebRip 1080p HEVC-10bit AAC][中日双语字幕]")] public void should_parse_language_chinese(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Chinese.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Chinese.Id); } [TestCase("Title.the.Series.2009.S01E14.Korean.HDTV.XviD-LOL")] public void should_parse_language_korean(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Korean.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Korean.Id); } [TestCase("Title.the.Series.2009.S01E14.Russian.HDTV.XviD-LOL")] [TestCase("Title.the.Series.S01E01.1080p.WEB-DL.Rus.Eng.TVKlondike")] public void should_parse_language_russian(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Russian.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Russian.Id); } [TestCase("Title.the.Series.2009.S01E14.Polish.HDTV.XviD-LOL")] @@ -165,64 +166,64 @@ public void should_parse_language_russian(string postTitle) [TestCase("Title.the.Series.2009.S01E14.DUB-PL.HDTV.XviD-LOL")] public void should_parse_language_polish(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Polish.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Polish.Id); } [TestCase("Title.the.Series.2009.S01E14.Vietnamese.HDTV.XviD-LOL")] public void should_parse_language_vietnamese(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Vietnamese.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Vietnamese.Id); } [TestCase("Title.the.Series.2009.S01E14.Swedish.HDTV.XviD-LOL")] public void should_parse_language_swedish(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Swedish.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Swedish.Id); } [TestCase("Title.the.Series.2009.S01E14.Norwegian.HDTV.XviD-LOL")] public void should_parse_language_norwegian(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Norwegian.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Norwegian.Id); } [TestCase("Title.the.Series.2009.S01E14.Finnish.HDTV.XviD-LOL")] public void should_parse_language_finnish(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Finnish.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Finnish.Id); } [TestCase("Title.the.Series.2009.S01E14.Turkish.HDTV.XviD-LOL")] public void should_parse_language_turkish(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Turkish.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Turkish.Id); } [TestCase("Title.the.Series.2009.S01E14.Portuguese.HDTV.XviD-LOL")] public void should_parse_language_portuguese(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Portuguese.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Portuguese.Id); } [TestCase("Title.the.Series.S01E01.FLEMISH.HDTV.x264-BRiGAND")] public void should_parse_language_flemish(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Flemish.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Flemish.Id); } [TestCase("Title.the.Series.S03E13.Greek.PDTV.XviD-Ouzo")] public void should_parse_language_greek(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Greek.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Greek.Id); } [TestCase("Title.the.Series.2009.S01E14.HDTV.XviD.HUNDUB-LOL")] @@ -230,44 +231,44 @@ public void should_parse_language_greek(string postTitle) [TestCase("Title.the.Series.2009.S01E14.HDTV.XviD.HUN-LOL")] public void should_parse_language_hungarian(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Hungarian.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Hungarian.Id); } [TestCase("Title.the.Series.S01-03.DVDRip.HebDub")] public void should_parse_language_hebrew(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Hebrew.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Hebrew.Id); } [TestCase("Title.the.Series.S05E01.WEBRip.x264.AC3.LT.EN-CNN")] public void should_parse_language_lithuanian(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Lithuanian.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Lithuanian.Id); } [TestCase("Title.the.Series.​S07E11.​WEB Rip.​XviD.​Louige-​CZ.​EN.​5.​1")] public void should_parse_language_czech(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Czech.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Czech.Id); } [TestCase("Series Title.S01.ARABIC.COMPLETE.720p.NF.WEBRip.x264-PTV")] public void should_parse_language_arabic(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Arabic.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Arabic.Id); } [TestCase("The Shadow Series S01 E01-08 WebRip Dual Audio [Hindi 5.1 + English 5.1] 720p x264 AAC ESub")] [TestCase("The Final Sonarr (2020) S04 Complete 720p NF WEBRip [Hindi+English] Dual audio")] public void should_parse_language_hindi(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Hindi.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Hindi.Id); } [TestCase("Title.the.Series.2009.S01E14.Bulgarian.HDTV.XviD-LOL")] @@ -275,8 +276,8 @@ public void should_parse_language_hindi(string postTitle) [TestCase("Title.the.Series.2009.S01E14.BG.AUDIO.HDTV.XviD-LOL")] public void should_parse_language_bulgarian(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Bulgarian.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Bulgarian.Id); } [TestCase("Series Title S01E01 Malayalam.1080p.WebRip.AVC.5.1-Rjaa")] @@ -284,8 +285,8 @@ public void should_parse_language_bulgarian(string postTitle) [TestCase("Series.Title.S01E01.DVDRip.1CD.Malayalam.Xvid.MP3 @Mastitorrents")] public void should_parse_language_malayalam(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Malayalam.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Malayalam.Id); } [TestCase("Гало(Сезон 1, серії 1-5) / SeriesTitle(Season 1, episodes 1-5) (2022) WEBRip-AVC Ukr/Eng")] @@ -293,8 +294,8 @@ public void should_parse_language_malayalam(string postTitle) [TestCase("Книга Боби Фетта(Сезон 1) / Series Title(Season 1) (2021) WEB-DLRip Ukr/Eng")] public void should_parse_language_ukrainian(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Id.Should().Be(Language.Ukrainian.Id); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Id.Should().Be(Language.Ukrainian.Id); } [TestCase("Title.the.Russian.Series.S01E07.Cold.Action.HDTV.XviD-Droned")] @@ -304,8 +305,8 @@ public void should_parse_language_ukrainian(string postTitle) [TestCase("Title The Spanish S02E02 Flodden 720p AMZN WEB-DL DDP5 1 H 264-NTb")] public void should_not_parse_series_or_episode_title(string postTitle) { - var result = LanguageParser.ParseLanguage(postTitle); - result.Name.Should().Be(Language.English.Name); + var result = LanguageParser.ParseLanguages(postTitle); + result.First().Name.Should().Be(Language.English.Name); } } } diff --git a/src/NzbDrone.Core.Test/Profiles/QualityProfileServiceFixture.cs b/src/NzbDrone.Core.Test/Profiles/QualityProfileServiceFixture.cs index f04b45a9c..9dc0ec291 100644 --- a/src/NzbDrone.Core.Test/Profiles/QualityProfileServiceFixture.cs +++ b/src/NzbDrone.Core.Test/Profiles/QualityProfileServiceFixture.cs @@ -101,7 +101,6 @@ public void should_not_be_able_to_delete_profile_if_assigned_to_import_list() var importLists = Builder.CreateListOfSize(3) .Random(1) - .With(c => c.LanguageProfileId = 1) .Build().ToList(); Mocker.GetMock().Setup(c => c.Get(profile.Id)).Returns(profile); diff --git a/src/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs b/src/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs index 7d447ea6b..980107237 100644 --- a/src/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs +++ b/src/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using FizzWare.NBuilder; using FluentAssertions; @@ -38,7 +39,7 @@ public void Setup() _episodeFile = Builder.CreateNew() .With(e => e.SeriesId = _series.Id) .With(e => e.Quality = new QualityModel(Quality.HDTV720p)) - .With(e => e.Language = Language.English) + .With(e => e.Languages = new List { Language.English }) .BuildNew(); } diff --git a/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesRepositoryReadFixture.cs b/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesRepositoryReadFixture.cs index 87ebf802b..ea6587dbf 100644 --- a/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesRepositoryReadFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesRepositoryReadFixture.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; @@ -29,7 +30,7 @@ public void should_get_episodes_by_file() { var episodeFile = Builder.CreateNew() .With(h => h.Quality = new QualityModel()) - .With(h => h.Language = Language.English) + .With(h => h.Languages = new List { Language.English }) .BuildNew(); Db.Insert(episodeFile); diff --git a/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWhereCutoffUnmetFixture.cs b/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWhereCutoffUnmetFixture.cs index 873af5b12..7e4b977f0 100644 --- a/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWhereCutoffUnmetFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWhereCutoffUnmetFixture.cs @@ -7,7 +7,6 @@ using NzbDrone.Core.Datastore; using NzbDrone.Core.Languages; using NzbDrone.Core.MediaFiles; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; @@ -40,20 +39,12 @@ public void Setup() } }; - var langProfile = new LanguageProfile - { - Id = 1, - Languages = Languages.LanguageFixture.GetDefaultLanguages(), - Cutoff = Language.Spanish - }; - _monitoredSeries = Builder.CreateNew() .With(s => s.TvRageId = RandomNumber) .With(s => s.Runtime = 30) .With(s => s.Monitored = true) .With(s => s.TitleSlug = "Title3") .With(s => s.QualityProfileId = profile.Id) - .With(s => s.LanguageProfileId = langProfile.Id) .BuildNew(); _unmonitoredSeries = Builder.CreateNew() @@ -62,7 +53,6 @@ public void Setup() .With(s => s.Monitored = false) .With(s => s.TitleSlug = "Title2") .With(s => s.QualityProfileId = profile.Id) - .With(s => s.LanguageProfileId = langProfile.Id) .BuildNew(); _monitoredSeries.Id = Db.Insert(_monitoredSeries).Id; @@ -86,15 +76,15 @@ public void Setup() new LanguagesBelowCutoff(profile.Id, new[] { Language.English.Id }) }; - var qualityMetLanguageUnmet = new EpisodeFile { RelativePath = "a", Quality = new QualityModel { Quality = Quality.WEBDL480p }, Language = Language.English }; - var qualityMetLanguageMet = new EpisodeFile { RelativePath = "b", Quality = new QualityModel { Quality = Quality.WEBDL480p }, Language = Language.Spanish }; - var qualityMetLanguageExceed = new EpisodeFile { RelativePath = "c", Quality = new QualityModel { Quality = Quality.WEBDL480p }, Language = Language.French }; - var qualityUnmetLanguageUnmet = new EpisodeFile { RelativePath = "d", Quality = new QualityModel { Quality = Quality.SDTV }, Language = Language.English }; - var qualityUnmetLanguageMet = new EpisodeFile { RelativePath = "e", Quality = new QualityModel { Quality = Quality.SDTV }, Language = Language.Spanish }; - var qualityUnmetLanguageExceed = new EpisodeFile { RelativePath = "f", Quality = new QualityModel { Quality = Quality.SDTV }, Language = Language.French }; - var qualityRawHDLanguageUnmet = new EpisodeFile { RelativePath = "g", Quality = new QualityModel { Quality = Quality.RAWHD }, Language = Language.English }; - var qualityRawHDLanguageMet = new EpisodeFile { RelativePath = "h", Quality = new QualityModel { Quality = Quality.RAWHD }, Language = Language.Spanish }; - var qualityRawHDLanguageExceed = new EpisodeFile { RelativePath = "i", Quality = new QualityModel { Quality = Quality.RAWHD }, Language = Language.French }; + var qualityMetLanguageUnmet = new EpisodeFile { RelativePath = "a", Quality = new QualityModel { Quality = Quality.WEBDL480p }, Languages = new List { Language.English } }; + var qualityMetLanguageMet = new EpisodeFile { RelativePath = "b", Quality = new QualityModel { Quality = Quality.WEBDL480p }, Languages = new List { Language.Spanish } }; + var qualityMetLanguageExceed = new EpisodeFile { RelativePath = "c", Quality = new QualityModel { Quality = Quality.WEBDL480p }, Languages = new List { Language.French } }; + var qualityUnmetLanguageUnmet = new EpisodeFile { RelativePath = "d", Quality = new QualityModel { Quality = Quality.SDTV }, Languages = new List { Language.English } }; + var qualityUnmetLanguageMet = new EpisodeFile { RelativePath = "e", Quality = new QualityModel { Quality = Quality.SDTV }, Languages = new List { Language.Spanish } }; + var qualityUnmetLanguageExceed = new EpisodeFile { RelativePath = "f", Quality = new QualityModel { Quality = Quality.SDTV }, Languages = new List { Language.French } }; + var qualityRawHDLanguageUnmet = new EpisodeFile { RelativePath = "g", Quality = new QualityModel { Quality = Quality.RAWHD }, Languages = new List { Language.English } }; + var qualityRawHDLanguageMet = new EpisodeFile { RelativePath = "h", Quality = new QualityModel { Quality = Quality.RAWHD }, Languages = new List { Language.Spanish } }; + var qualityRawHDLanguageExceed = new EpisodeFile { RelativePath = "i", Quality = new QualityModel { Quality = Quality.RAWHD }, Languages = new List { Language.French } }; MediaFileRepository fileRepository = Mocker.Resolve(); diff --git a/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithFilesFixture.cs b/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithFilesFixture.cs index c545f362f..b468effa2 100644 --- a/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithFilesFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithFilesFixture.cs @@ -24,7 +24,7 @@ public void Setup() _episodeFiles = Builder.CreateListOfSize(5) .All() .With(c => c.Quality = new QualityModel()) - .With(c => c.Language = Language.English) + .With(c => c.Languages = new List { Language.English }) .BuildListOfNew(); Db.InsertMany(_episodeFiles); @@ -59,7 +59,7 @@ public void should_only_contain_episodes_for_the_given_series() var episodeFile = Builder.CreateNew() .With(f => f.RelativePath = "another path") .With(c => c.Quality = new QualityModel()) - .With(c => c.Language = Language.English) + .With(c => c.Languages = new List { Language.English }) .BuildNew(); Db.Insert(episodeFile); diff --git a/src/NzbDrone.Core.Test/TvTests/SeriesRepositoryTests/SeriesRepositoryFixture.cs b/src/NzbDrone.Core.Test/TvTests/SeriesRepositoryTests/SeriesRepositoryFixture.cs index c347e8503..619b56e17 100644 --- a/src/NzbDrone.Core.Test/TvTests/SeriesRepositoryTests/SeriesRepositoryFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/SeriesRepositoryTests/SeriesRepositoryFixture.cs @@ -3,7 +3,6 @@ using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Languages; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; @@ -26,24 +25,14 @@ public void should_lazyload_quality_profile() Name = "TestProfile" }; - var langProfile = new LanguageProfile - { - Name = "TestProfile", - Languages = Languages.LanguageFixture.GetDefaultLanguages(Language.English), - Cutoff = Language.English - }; - Mocker.Resolve().Insert(profile); - Mocker.Resolve().Insert(langProfile); var series = Builder.CreateNew().BuildNew(); series.QualityProfileId = profile.Id; - series.LanguageProfileId = langProfile.Id; Subject.Insert(series); StoredModel.QualityProfile.Should().NotBeNull(); - StoredModel.LanguageProfile.Should().NotBeNull(); } private void GivenSeries() diff --git a/src/NzbDrone.Core/Blocklisting/Blocklist.cs b/src/NzbDrone.Core/Blocklisting/Blocklist.cs index b70f5d53d..5d90a9514 100644 --- a/src/NzbDrone.Core/Blocklisting/Blocklist.cs +++ b/src/NzbDrone.Core/Blocklisting/Blocklist.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using NzbDrone.Core.Datastore; using NzbDrone.Core.Indexers; @@ -22,6 +22,6 @@ public class Blocklist : ModelBase public string Indexer { get; set; } public string Message { get; set; } public string TorrentInfoHash { get; set; } - public Language Language { get; set; } + public List Languages { get; set; } } } diff --git a/src/NzbDrone.Core/Blocklisting/BlocklistService.cs b/src/NzbDrone.Core/Blocklisting/BlocklistService.cs index 33b84dfb8..c349b09d0 100644 --- a/src/NzbDrone.Core/Blocklisting/BlocklistService.cs +++ b/src/NzbDrone.Core/Blocklisting/BlocklistService.cs @@ -80,7 +80,7 @@ public void Block(RemoteEpisode remoteEpisode, string message) Indexer = remoteEpisode.Release.Indexer, Protocol = remoteEpisode.Release.DownloadProtocol, Message = message, - Language = remoteEpisode.ParsedEpisodeInfo.Language + Languages = remoteEpisode.ParsedEpisodeInfo.Languages }; if (remoteEpisode.Release is TorrentInfo torrentRelease) @@ -181,7 +181,7 @@ public void Handle(DownloadFailedEvent message) Protocol = (DownloadProtocol)Convert.ToInt32(message.Data.GetValueOrDefault("protocol")), Message = message.Message, TorrentInfoHash = message.Data.GetValueOrDefault("torrentInfoHash"), - Language = message.Language + Languages = message.Languages }; _blocklistRepository.Insert(blocklist); diff --git a/src/NzbDrone.Core/CustomFormats/CustomFormatCalculationService.cs b/src/NzbDrone.Core/CustomFormats/CustomFormatCalculationService.cs index 5fb0aaf5b..6b7546db0 100644 --- a/src/NzbDrone.Core/CustomFormats/CustomFormatCalculationService.cs +++ b/src/NzbDrone.Core/CustomFormats/CustomFormatCalculationService.cs @@ -79,7 +79,7 @@ public static List ParseCustomFormat(EpisodeFile episodeFile, List SeriesTitle = episodeFile.Series.Value.Title, ReleaseTitle = sceneName, Quality = episodeFile.Quality, - Language = episodeFile.Language, + Languages = episodeFile.Languages, ReleaseGroup = episodeFile.ReleaseGroup, ExtraInfo = new Dictionary { @@ -111,7 +111,7 @@ public List ParseCustomFormat(Blocklist blocklist) SeriesTitle = movie.Title, ReleaseTitle = parsed?.ReleaseTitle ?? blocklist.SourceTitle, Quality = blocklist.Quality, - Language = blocklist.Language, + Languages = blocklist.Languages, ReleaseGroup = parsed?.ReleaseGroup, ExtraInfo = new Dictionary { @@ -134,7 +134,7 @@ public List ParseCustomFormat(EpisodeHistory history) SeriesTitle = movie.Title, ReleaseTitle = parsed?.ReleaseTitle ?? history.SourceTitle, Quality = history.Quality, - Language = history.Language, + Languages = history.Languages, ReleaseGroup = parsed?.ReleaseGroup, ExtraInfo = new Dictionary { diff --git a/src/NzbDrone.Core/CustomFormats/Specifications/CustomFormatSpecificationBase.cs b/src/NzbDrone.Core/CustomFormats/Specifications/CustomFormatSpecificationBase.cs index 203b4dd4f..5c0bf4d27 100644 --- a/src/NzbDrone.Core/CustomFormats/Specifications/CustomFormatSpecificationBase.cs +++ b/src/NzbDrone.Core/CustomFormats/Specifications/CustomFormatSpecificationBase.cs @@ -1,4 +1,5 @@ using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Validation; namespace NzbDrone.Core.CustomFormats { @@ -18,6 +19,8 @@ public ICustomFormatSpecification Clone() return (ICustomFormatSpecification)MemberwiseClone(); } + public abstract NzbDroneValidationResult Validate(); + public bool IsSatisfiedBy(ParsedEpisodeInfo episodeInfo) { var match = IsSatisfiedByWithoutNegate(episodeInfo); diff --git a/src/NzbDrone.Core/CustomFormats/Specifications/ICustomFormatSpecification.cs b/src/NzbDrone.Core/CustomFormats/Specifications/ICustomFormatSpecification.cs index 2e7263cf1..d2aabcd0a 100644 --- a/src/NzbDrone.Core/CustomFormats/Specifications/ICustomFormatSpecification.cs +++ b/src/NzbDrone.Core/CustomFormats/Specifications/ICustomFormatSpecification.cs @@ -1,4 +1,5 @@ using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Validation; namespace NzbDrone.Core.CustomFormats { @@ -11,6 +12,8 @@ public interface ICustomFormatSpecification bool Negate { get; set; } bool Required { get; set; } + NzbDroneValidationResult Validate(); + ICustomFormatSpecification Clone(); bool IsSatisfiedBy(ParsedEpisodeInfo episodeInfo); } diff --git a/src/NzbDrone.Core/CustomFormats/Specifications/LanguageSpecification.cs b/src/NzbDrone.Core/CustomFormats/Specifications/LanguageSpecification.cs new file mode 100644 index 000000000..341fb4d62 --- /dev/null +++ b/src/NzbDrone.Core/CustomFormats/Specifications/LanguageSpecification.cs @@ -0,0 +1,45 @@ +using System.Linq; +using FluentValidation; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.CustomFormats +{ + public class LanguageSpecificationValidator : AbstractValidator + { + public LanguageSpecificationValidator() + { + RuleFor(c => c.Value).NotEmpty(); + RuleFor(c => c.Value).Custom((value, context) => + { + if (!Language.All.Any(o => o.Id == value)) + { + context.AddFailure(string.Format("Invalid Language condition value: {0}", value)); + } + }); + } + } + + public class LanguageSpecification : CustomFormatSpecificationBase + { + private static readonly LanguageSpecificationValidator Validator = new LanguageSpecificationValidator(); + + public override int Order => 3; + public override string ImplementationName => "Language"; + + [FieldDefinition(1, Label = "Language", Type = FieldType.Select, SelectOptions = typeof(LanguageFieldConverter))] + public int Value { get; set; } + + protected override bool IsSatisfiedByWithoutNegate(ParsedEpisodeInfo episodeInfo) + { + return episodeInfo?.Languages?.Contains((Language)Value) ?? false; + } + + public override NzbDroneValidationResult Validate() + { + return new NzbDroneValidationResult(Validator.Validate(this)); + } + } +} diff --git a/src/NzbDrone.Core/CustomFormats/Specifications/RegexSpecificationBase.cs b/src/NzbDrone.Core/CustomFormats/Specifications/RegexSpecificationBase.cs index 030dff60a..ead5481eb 100644 --- a/src/NzbDrone.Core/CustomFormats/Specifications/RegexSpecificationBase.cs +++ b/src/NzbDrone.Core/CustomFormats/Specifications/RegexSpecificationBase.cs @@ -1,10 +1,23 @@ using System.Text.RegularExpressions; +using FluentValidation; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Annotations; +using NzbDrone.Core.Validation; namespace NzbDrone.Core.CustomFormats { + public class RegexSpecificationBaseValidator : AbstractValidator + { + public RegexSpecificationBaseValidator() + { + RuleFor(c => c.Value).NotEmpty().WithMessage("Regex Pattern must not be empty"); + } + } + public abstract class RegexSpecificationBase : CustomFormatSpecificationBase { + private static readonly RegexSpecificationBaseValidator Validator = new RegexSpecificationBaseValidator(); + protected Regex _regex; protected string _raw; @@ -15,7 +28,11 @@ public string Value set { _raw = value; - _regex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase); + + if (value.IsNotNullOrWhiteSpace()) + { + _regex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase); + } } } @@ -28,5 +45,10 @@ protected bool MatchString(string compared) return _regex.IsMatch(compared); } + + public override NzbDroneValidationResult Validate() + { + return new NzbDroneValidationResult(Validator.Validate(this)); + } } } diff --git a/src/NzbDrone.Core/CustomFormats/Specifications/ResolutionSpecification.cs b/src/NzbDrone.Core/CustomFormats/Specifications/ResolutionSpecification.cs index b45e6a07a..c28a9f2b6 100644 --- a/src/NzbDrone.Core/CustomFormats/Specifications/ResolutionSpecification.cs +++ b/src/NzbDrone.Core/CustomFormats/Specifications/ResolutionSpecification.cs @@ -1,11 +1,23 @@ +using FluentValidation; using NzbDrone.Core.Annotations; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Validation; namespace NzbDrone.Core.CustomFormats { + public class ResolutionSpecificationValidator : AbstractValidator + { + public ResolutionSpecificationValidator() + { + RuleFor(c => c.Value).NotEmpty(); + } + } + public class ResolutionSpecification : CustomFormatSpecificationBase { + private static readonly ResolutionSpecificationValidator Validator = new ResolutionSpecificationValidator(); + public override int Order => 6; public override string ImplementationName => "Resolution"; @@ -16,5 +28,10 @@ protected override bool IsSatisfiedByWithoutNegate(ParsedEpisodeInfo episodeInfo { return (episodeInfo?.Quality?.Quality?.Resolution ?? (int)Resolution.Unknown) == Value; } + + public override NzbDroneValidationResult Validate() + { + return new NzbDroneValidationResult(Validator.Validate(this)); + } } } diff --git a/src/NzbDrone.Core/CustomFormats/Specifications/SizeSpecification.cs b/src/NzbDrone.Core/CustomFormats/Specifications/SizeSpecification.cs index bb2d82553..b0e3402bc 100644 --- a/src/NzbDrone.Core/CustomFormats/Specifications/SizeSpecification.cs +++ b/src/NzbDrone.Core/CustomFormats/Specifications/SizeSpecification.cs @@ -1,12 +1,25 @@ using System.Collections.Generic; +using FluentValidation; using NzbDrone.Common.Extensions; using NzbDrone.Core.Annotations; using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Validation; namespace NzbDrone.Core.CustomFormats { + public class SizeSpecificationValidator : AbstractValidator + { + public SizeSpecificationValidator() + { + RuleFor(c => c.Min).GreaterThanOrEqualTo(0); + RuleFor(c => c.Max).GreaterThan(c => c.Min); + } + } + public class SizeSpecification : CustomFormatSpecificationBase { + private static readonly SizeSpecificationValidator Validator = new SizeSpecificationValidator(); + public override int Order => 8; public override string ImplementationName => "Size"; @@ -22,5 +35,10 @@ protected override bool IsSatisfiedByWithoutNegate(ParsedEpisodeInfo episodeInfo return size > Min.Gigabytes() && size <= Max.Gigabytes(); } + + public override NzbDroneValidationResult Validate() + { + return new NzbDroneValidationResult(Validator.Validate(this)); + } } } diff --git a/src/NzbDrone.Core/CustomFormats/Specifications/SourceSpecification.cs b/src/NzbDrone.Core/CustomFormats/Specifications/SourceSpecification.cs index 368ba0390..5c1fbcf3b 100644 --- a/src/NzbDrone.Core/CustomFormats/Specifications/SourceSpecification.cs +++ b/src/NzbDrone.Core/CustomFormats/Specifications/SourceSpecification.cs @@ -1,11 +1,23 @@ +using FluentValidation; using NzbDrone.Core.Annotations; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; +using NzbDrone.Core.Validation; namespace NzbDrone.Core.CustomFormats { + public class SourceSpecificationValidator : AbstractValidator + { + public SourceSpecificationValidator() + { + RuleFor(c => c.Value).NotEmpty(); + } + } + public class SourceSpecification : CustomFormatSpecificationBase { + private static readonly SourceSpecificationValidator Validator = new SourceSpecificationValidator(); + public override int Order => 5; public override string ImplementationName => "Source"; @@ -16,5 +28,10 @@ protected override bool IsSatisfiedByWithoutNegate(ParsedEpisodeInfo episodeInfo { return (episodeInfo?.Quality?.Quality?.Source ?? (int)QualitySource.Unknown) == (QualitySource)Value; } + + public override NzbDroneValidationResult Validate() + { + return new NzbDroneValidationResult(Validator.Validate(this)); + } } } diff --git a/src/NzbDrone.Core/Datastore/Migration/175_language_profiles_to_custom_formats.cs b/src/NzbDrone.Core/Datastore/Migration/175_language_profiles_to_custom_formats.cs new file mode 100644 index 000000000..c9ebad6be --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/175_language_profiles_to_custom_formats.cs @@ -0,0 +1,37 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(175)] + public class language_profiles_to_custom_formats : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("EpisodeFiles") + .AddColumn("Languages").AsString().NotNullable().WithDefaultValue("[]"); + + Alter.Table("History") + .AddColumn("Languages").AsString().NotNullable().WithDefaultValue("[]"); + + Alter.Table("Blocklist") + .AddColumn("Languages").AsString().NotNullable().WithDefaultValue("[]"); + + //Migrate Language to Languages in all tables + Execute.Sql("UPDATE EpisodeFiles SET Languages = '[' || Language || ']'"); + Execute.Sql("UPDATE History SET Languages = '[' || Language || ']'"); + Execute.Sql("UPDATE Blocklist SET Languages = '[' || Language || ']'"); + + //Migrate Language Profiles to CFs + + Delete.Column("Language").FromTable("EpisodeFiles"); + Delete.Column("Language").FromTable("History"); + Delete.Column("Language").FromTable("Blocklist"); + + Delete.Column("LanguageProfileId").FromTable("Series"); + Delete.Column("LanguageProfileId").FromTable("ImportLists"); + + Delete.Table("LanguageProfiles"); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index b5997fcd3..46cdf5c1b 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -31,7 +31,6 @@ using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Profiles; using NzbDrone.Core.Profiles.Delay; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Profiles.Releases; using NzbDrone.Core.Qualities; @@ -109,8 +108,7 @@ public static void Map() Mapper.Entity("Series").RegisterModel() .Ignore(s => s.RootFolderPath) - .HasOne(s => s.QualityProfile, s => s.QualityProfileId) - .HasOne(s => s.LanguageProfile, s => s.LanguageProfileId); + .HasOne(s => s.QualityProfile, s => s.QualityProfileId); Mapper.Entity("EpisodeFiles").RegisterModel() .HasOne(f => f.Series, f => f.SeriesId) @@ -132,7 +130,6 @@ public static void Map() Mapper.Entity("CustomFormats").RegisterModel(); Mapper.Entity("QualityProfiles").RegisterModel(); - Mapper.Entity("LanguageProfiles").RegisterModel(); Mapper.Entity("Logs").RegisterModel(); Mapper.Entity("NamingConfig").RegisterModel(); Mapper.Entity("Blocklist").RegisterModel(); @@ -174,7 +171,6 @@ private static void RegisterMappers() SqlMapper.AddTypeHandler(new DapperQualityIntConverter()); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>(new QualityIntConverter())); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>(new CustomFormatIntConverter())); - SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>(new LanguageIntConverter())); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>(new CustomFormatSpecificationListConverter())); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter(new QualityIntConverter())); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>()); diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs index b2cdca37a..e2d19ad24 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs @@ -29,7 +29,6 @@ public int Compare(DownloadDecision x, DownloadDecision y) var comparers = new List { CompareQuality, - CompareLanguage, CompareCustomFormatScore, CompareProtocol, CompareEpisodeCount, @@ -80,11 +79,6 @@ private int CompareQuality(DownloadDecision x, DownloadDecision y) CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.ParsedEpisodeInfo.Quality.Revision)); } - private int CompareLanguage(DownloadDecision x, DownloadDecision y) - { - return CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.Series.LanguageProfile.Value.Languages.FindIndex(l => l.Language == remoteEpisode.ParsedEpisodeInfo.Language)); - } - private int CompareCustomFormatScore(DownloadDecision x, DownloadDecision y) { return CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteMovie => remoteMovie.CustomFormatScore); diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs index 77c2323e5..373cab9ed 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs @@ -126,7 +126,7 @@ private IEnumerable GetDecisions(List reports, Se { parsedEpisodeInfo = new ParsedEpisodeInfo { - Language = LanguageParser.ParseLanguage(report.Title), + Languages = LanguageParser.ParseLanguages(report.Title), Quality = QualityParser.ParseQuality(report.Title) }; } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs index 95f17538c..c1ff68bfd 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs @@ -27,7 +27,6 @@ public CutoffSpecification(UpgradableSpecification upgradableSpecification, ICus public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { var qualityProfile = subject.Series.QualityProfile.Value; - var languageProfile = subject.Series.LanguageProfile.Value; foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value)) { @@ -37,14 +36,12 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase continue; } - _logger.Debug("Comparing file quality and language with report. Existing file is {0} - {1}", file.Quality, file.Language); + _logger.Debug("Comparing file quality with report. Existing file is {0} - {1}", file.Quality); var customFormats = _formatService.ParseCustomFormat(file); if (!_upgradableSpecification.CutoffNotMet(qualityProfile, - languageProfile, file.Quality, - file.Language, _formatService.ParseCustomFormat(file), subject.ParsedEpisodeInfo.Quality)) { @@ -53,7 +50,7 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase var qualityCutoffIndex = qualityProfile.GetIndex(qualityProfile.Cutoff); var qualityCutoff = qualityProfile.Items[qualityCutoffIndex.Index]; - return Decision.Reject("Existing file meets cutoff: {0} - {1}", qualityCutoff, languageProfile.Cutoff); + return Decision.Reject("Existing file meets cutoff: {0}", qualityCutoff); } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs deleted file mode 100644 index 5a98fb28b..000000000 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Linq; -using NLog; -using NzbDrone.Core.IndexerSearch.Definitions; -using NzbDrone.Core.Parser.Model; - -namespace NzbDrone.Core.DecisionEngine.Specifications -{ - public class LanguageSpecification : IDecisionEngineSpecification - { - private readonly Logger _logger; - - public LanguageSpecification(Logger logger) - { - _logger = logger; - } - - public SpecificationPriority Priority => SpecificationPriority.Default; - public RejectionType Type => RejectionType.Permanent; - - public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) - { - var wantedLanguage = subject.Series.LanguageProfile.Value.Languages; - var language = subject.ParsedEpisodeInfo.Language; - - _logger.Debug("Checking if report meets language requirements. {0}", subject.ParsedEpisodeInfo.Language); - - if (!wantedLanguage.Exists(v => v.Allowed && v.Language == language)) - { - _logger.Debug("Report Language: {0} rejected because it is not wanted in profile {1}", language, subject.Series.LanguageProfile.Value.Name); - return Decision.Reject("{0} is not allowed in profile {1}", language, subject.Series.LanguageProfile.Value.Name); - } - - return Decision.Accept(); - } - } -} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs index 4da8791ff..a8c56e689 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs @@ -42,7 +42,6 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr { var remoteEpisode = queueItem.RemoteEpisode; var qualityProfile = subject.Series.QualityProfile.Value; - var languageProfile = subject.Series.LanguageProfile.Value; // To avoid a race make sure it's not FailedPending (failed awaiting removal/search). // Failed items (already searching for a replacement) won't be part of the queue since @@ -55,42 +54,34 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr var queuedItemCustomFormats = _formatService.ParseCustomFormat(remoteEpisode.ParsedEpisodeInfo); - _logger.Debug("Checking if existing release in queue meets cutoff. Queued: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language); + _logger.Debug("Checking if existing release in queue meets cutoff. Queued: {0}", remoteEpisode.ParsedEpisodeInfo.Quality); if (!_upgradableSpecification.CutoffNotMet(qualityProfile, - languageProfile, remoteEpisode.ParsedEpisodeInfo.Quality, - remoteEpisode.ParsedEpisodeInfo.Language, queuedItemCustomFormats, subject.ParsedEpisodeInfo.Quality)) { - return Decision.Reject("Release in queue already meets cutoff: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language); + return Decision.Reject("Release in queue already meets cutoff: {0}", remoteEpisode.ParsedEpisodeInfo.Quality); } - _logger.Debug("Checking if release is higher quality than queued release. Queued: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language); + _logger.Debug("Checking if release is higher quality than queued release. Queued: {0}", remoteEpisode.ParsedEpisodeInfo.Quality); if (!_upgradableSpecification.IsUpgradable(qualityProfile, - languageProfile, remoteEpisode.ParsedEpisodeInfo.Quality, - remoteEpisode.ParsedEpisodeInfo.Language, remoteEpisode.CustomFormats, subject.ParsedEpisodeInfo.Quality, - subject.ParsedEpisodeInfo.Language, subject.CustomFormats)) { - return Decision.Reject("Release in queue is of equal or higher preference: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language); + return Decision.Reject("Release in queue is of equal or higher preference: {0}", remoteEpisode.ParsedEpisodeInfo.Quality); } - _logger.Debug("Checking if profiles allow upgrading. Queued: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language); + _logger.Debug("Checking if profiles allow upgrading. Queued: {0}", remoteEpisode.ParsedEpisodeInfo.Quality); if (!_upgradableSpecification.IsUpgradeAllowed(subject.Series.QualityProfile, - subject.Series.LanguageProfile, remoteEpisode.ParsedEpisodeInfo.Quality, - remoteEpisode.ParsedEpisodeInfo.Language, - subject.ParsedEpisodeInfo.Quality, - subject.ParsedEpisodeInfo.Language)) + subject.ParsedEpisodeInfo.Quality)) { - return Decision.Reject("Another release is queued and the Quality or Language profile does not allow upgrades"); + return Decision.Reject("Another release is queued and the Quality profile does not allow upgrades"); } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs index 45b5637cc..e0954f166 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs @@ -2,7 +2,6 @@ using NLog; using NzbDrone.Core.Download.Pending; using NzbDrone.Core.IndexerSearch.Definitions; -using NzbDrone.Core.Languages; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Profiles.Delay; using NzbDrone.Core.Qualities; @@ -36,7 +35,6 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase } var qualityProfile = subject.Series.QualityProfile.Value; - var languageProfile = subject.Series.LanguageProfile.Value; var delayProfile = _delayProfileService.BestForTags(subject.Series.Tags); var delay = delayProfile.GetProtocolDelay(subject.Release.DownloadProtocol); var isPreferredProtocol = subject.Release.DownloadProtocol == delayProfile.PreferredProtocol; @@ -48,7 +46,6 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase } var qualityComparer = new QualityModelComparer(qualityProfile); - var languageComparer = new LanguageComparer(languageProfile); if (isPreferredProtocol) { @@ -71,11 +68,10 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase { var bestQualityInProfile = qualityProfile.LastAllowedQuality(); var isBestInProfile = qualityComparer.Compare(subject.ParsedEpisodeInfo.Quality.Quality, bestQualityInProfile) >= 0; - var isBestInProfileLanguage = languageComparer.Compare(subject.ParsedEpisodeInfo.Language, languageProfile.LastAllowedLanguage()) >= 0; - if (isBestInProfile && isBestInProfileLanguage && isPreferredProtocol) + if (isBestInProfile && isPreferredProtocol) { - _logger.Debug("Quality and language is highest in profile for preferred protocol, will not delay"); + _logger.Debug("Quality is highest in profile for preferred protocol, will not delay"); return Decision.Accept(); } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs index 217a0dc2e..03b089327 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs @@ -65,20 +65,15 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase // Instead of fetching the series from the DB reuse the known series. var cutoffUnmet = _upgradableSpecification.CutoffNotMet( subject.Series.QualityProfile, - subject.Series.LanguageProfile, mostRecent.Quality, - mostRecent.Language, customFormats, subject.ParsedEpisodeInfo.Quality); var upgradeable = _upgradableSpecification.IsUpgradable( subject.Series.QualityProfile, - subject.Series.LanguageProfile, mostRecent.Quality, - mostRecent.Language, customFormats, subject.ParsedEpisodeInfo.Quality, - subject.ParsedEpisodeInfo.Language, subject.CustomFormats); if (!cutoffUnmet) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs index f080ffdc0..caf86023a 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs @@ -3,8 +3,6 @@ using NzbDrone.Common.Extensions; using NzbDrone.Core.Configuration; using NzbDrone.Core.CustomFormats; -using NzbDrone.Core.Languages; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; @@ -12,12 +10,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { public interface IUpgradableSpecification { - bool IsUpgradable(QualityProfile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, List currentCustomFormats, QualityModel newQuality, Language newLanguage, List newCustomFormats); + bool IsUpgradable(QualityProfile profile, QualityModel currentQuality, List currentCustomFormats, QualityModel newQuality, List newCustomFormats); bool QualityCutoffNotMet(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null); - bool LanguageCutoffNotMet(LanguageProfile languageProfile, Language currentLanguage); - bool CutoffNotMet(QualityProfile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, List currentCustomFormats, QualityModel newQuality = null); + bool CutoffNotMet(QualityProfile profile, QualityModel currentQuality, List currentCustomFormats, QualityModel newQuality = null); bool IsRevisionUpgrade(QualityModel currentQuality, QualityModel newQuality); - bool IsUpgradeAllowed(QualityProfile qualityProfile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, QualityModel newQuality, Language newLanguage); + bool IsUpgradeAllowed(QualityProfile qualityProfile, QualityModel currentQuality, QualityModel newQuality); } public class UpgradableSpecification : IUpgradableSpecification @@ -38,7 +35,7 @@ private bool IsPreferredWordUpgradable(int currentScore, int newScore) return newScore > currentScore; } - public bool IsUpgradable(QualityProfile qualityProfile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, List currentCustomFormats, QualityModel newQuality, Language newLanguage, List newCustomFormats) + public bool IsUpgradable(QualityProfile qualityProfile, QualityModel currentQuality, List currentCustomFormats, QualityModel newQuality, List newCustomFormats) { var qualityComparer = new QualityModelComparer(qualityProfile); var qualityCompare = qualityComparer.Compare(newQuality?.Quality, currentQuality.Quality); @@ -78,20 +75,6 @@ public bool IsUpgradable(QualityProfile qualityProfile, LanguageProfile language return false; } - var languageCompare = new LanguageComparer(languageProfile).Compare(newLanguage, currentLanguage); - - if (languageCompare > 0) - { - _logger.Debug("New item has a more preferred language"); - return true; - } - - if (languageCompare < 0) - { - _logger.Debug("Existing item has better language, skipping"); - return false; - } - if (newFormatScore <= currentFormatScore) { _logger.Debug("New item's custom formats [{0}] do not improve on [{1}], skipping", @@ -122,32 +105,14 @@ public bool QualityCutoffNotMet(QualityProfile profile, QualityModel currentQual return false; } - public bool LanguageCutoffNotMet(LanguageProfile languageProfile, Language currentLanguage) - { - var cutoff = languageProfile.UpgradeAllowed - ? languageProfile.Cutoff - : languageProfile.FirstAllowedLanguage(); - - var languageCompare = new LanguageComparer(languageProfile).Compare(currentLanguage, cutoff); - - return languageCompare < 0; - } - private bool CustomFormatCutoffNotMet(QualityProfile profile, List currentFormats) { var score = profile.CalculateCustomFormatScore(currentFormats); return score < profile.CutoffFormatScore; } - public bool CutoffNotMet(QualityProfile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, List currentFormats, QualityModel newQuality = null) + public bool CutoffNotMet(QualityProfile profile, QualityModel currentQuality, List currentFormats, QualityModel newQuality = null) { - // If we can upgrade the language (it is not the cutoff) then the quality doesn't - // matter as we can always get same quality with prefered language. - if (LanguageCutoffNotMet(languageProfile, currentLanguage)) - { - return true; - } - if (QualityCutoffNotMet(profile, currentQuality, newQuality)) { return true; @@ -177,13 +142,11 @@ public bool IsRevisionUpgrade(QualityModel currentQuality, QualityModel newQuali return false; } - public bool IsUpgradeAllowed(QualityProfile qualityProfile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, QualityModel newQuality, Language newLanguage) + public bool IsUpgradeAllowed(QualityProfile qualityProfile, QualityModel currentQuality, QualityModel newQuality) { var isQualityUpgrade = new QualityModelComparer(qualityProfile).Compare(newQuality, currentQuality) > 0; - var isLanguageUpgrade = new LanguageComparer(languageProfile).Compare(newLanguage, currentLanguage) > 0; - if ((isQualityUpgrade && qualityProfile.UpgradeAllowed) || - (isLanguageUpgrade && languageProfile.UpgradeAllowed)) + if (isQualityUpgrade && qualityProfile.UpgradeAllowed) { _logger.Debug("At least one profile allows upgrading"); return true; @@ -195,12 +158,6 @@ public bool IsUpgradeAllowed(QualityProfile qualityProfile, LanguageProfile lang return false; } - if (isLanguageUpgrade && !languageProfile.UpgradeAllowed) - { - _logger.Debug("Language profile does not allow upgrades, skipping"); - return false; - } - return true; } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeAllowedSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeAllowedSpecification.cs index a77feddde..08e1b0d1a 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeAllowedSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeAllowedSpecification.cs @@ -22,7 +22,6 @@ public UpgradeAllowedSpecification(UpgradableSpecification upgradableSpecificati public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { var qualityProfile = subject.Series.QualityProfile.Value; - var languageProfile = subject.Series.LanguageProfile.Value; foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value)) { @@ -32,18 +31,15 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase continue; } - _logger.Debug("Comparing file quality and language with report. Existing file is {0} - {1}", file.Quality, file.Language); + _logger.Debug("Comparing file quality with report. Existing file is {0} - {1}", file.Quality); if (!_upgradableSpecification.IsUpgradeAllowed(qualityProfile, - languageProfile, file.Quality, - file.Language, - subject.ParsedEpisodeInfo.Quality, - subject.ParsedEpisodeInfo.Language)) + subject.ParsedEpisodeInfo.Quality)) { - _logger.Debug("Upgrading is not allowed by the quality or language profile"); + _logger.Debug("Upgrading is not allowed by the quality profile"); - return Decision.Reject("Existing file and the Quality or Language profile does not allow upgrades"); + return Decision.Reject("Existing file and the Quality profile does not allow upgrades"); } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs index 5918192e4..b1c1e47e4 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs @@ -38,18 +38,15 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase var customFormats = _formatService.ParseCustomFormat(file); - _logger.Debug("Comparing file quality and language with report. Existing file is {0} - {1}", file.Quality, file.Language); + _logger.Debug("Comparing file quality with report. Existing file is {0}", file.Quality); if (!_upgradableSpecification.IsUpgradable(subject.Series.QualityProfile, - subject.Series.LanguageProfile, file.Quality, - file.Language, customFormats, subject.ParsedEpisodeInfo.Quality, - subject.ParsedEpisodeInfo.Language, subject.CustomFormats)) { - return Decision.Reject("Existing file on disk is of equal or higher preference: {0} - {1}", file.Quality, file.Language); + return Decision.Reject("Existing file on disk is of equal or higher preference: {0}", file.Quality); } } diff --git a/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs b/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs index bdcad90d5..c19ec494c 100644 --- a/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs +++ b/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs @@ -56,8 +56,8 @@ private static IEnumerable HandleTags(RemoteEpisode remoteEpisode, Flood case (int)AdditionalTags.Quality: result.Add(remoteEpisode.ParsedEpisodeInfo.Quality.Quality.ToString()); break; - case (int)AdditionalTags.Language: - result.Add(remoteEpisode.ParsedEpisodeInfo.Language.ToString()); + case (int)AdditionalTags.Languages: + result.UnionWith(remoteEpisode.ParsedEpisodeInfo.Languages.ConvertAll(language => language.ToString())); break; case (int)AdditionalTags.ReleaseGroup: result.Add(remoteEpisode.ParsedEpisodeInfo.ReleaseGroup); diff --git a/src/NzbDrone.Core/Download/Clients/Flood/Models/AdditionalTags.cs b/src/NzbDrone.Core/Download/Clients/Flood/Models/AdditionalTags.cs index bacf106dc..b03589652 100644 --- a/src/NzbDrone.Core/Download/Clients/Flood/Models/AdditionalTags.cs +++ b/src/NzbDrone.Core/Download/Clients/Flood/Models/AdditionalTags.cs @@ -11,7 +11,7 @@ public enum AdditionalTags Quality = 1, [FieldOption(Hint = "English")] - Language = 2, + Languages = 2, [FieldOption(Hint = "Example-Raws")] ReleaseGroup = 3, diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetProxy.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetProxy.cs index a24c464f4..13279ff91 100644 --- a/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetProxy.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; using NLog; using NzbDrone.Common.Cache; using NzbDrone.Common.Http; @@ -247,7 +248,7 @@ private T ProcessRequest(NzbgetSettings settings, string method, params objec throw new DownloadClientException("Unable to connect to NzbGet. " + ex.Message, ex); } - catch (WebException ex) + catch (HttpRequestException ex) { throw new DownloadClientUnavailableException("Unable to connect to NzbGet. " + ex.Message, ex); } diff --git a/src/NzbDrone.Core/Download/DownloadFailedEvent.cs b/src/NzbDrone.Core/Download/DownloadFailedEvent.cs index 4ebc84a84..143eba14e 100644 --- a/src/NzbDrone.Core/Download/DownloadFailedEvent.cs +++ b/src/NzbDrone.Core/Download/DownloadFailedEvent.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using NzbDrone.Common.Messaging; using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.Languages; @@ -22,6 +22,6 @@ public DownloadFailedEvent() public string Message { get; set; } public Dictionary Data { get; set; } public TrackedDownload TrackedDownload { get; set; } - public Language Language { get; set; } + public List Languages { get; set; } } } diff --git a/src/NzbDrone.Core/Download/DownloadIgnoredEvent.cs b/src/NzbDrone.Core/Download/DownloadIgnoredEvent.cs index ae887a87d..45ddbdaf8 100644 --- a/src/NzbDrone.Core/Download/DownloadIgnoredEvent.cs +++ b/src/NzbDrone.Core/Download/DownloadIgnoredEvent.cs @@ -10,7 +10,7 @@ public class DownloadIgnoredEvent : IEvent { public int SeriesId { get; set; } public List EpisodeIds { get; set; } - public Language Language { get; set; } + public List Languages { get; set; } public QualityModel Quality { get; set; } public string SourceTitle { get; set; } public DownloadClientItemClientInfo DownloadClientInfo { get; set; } diff --git a/src/NzbDrone.Core/Download/FailedDownloadService.cs b/src/NzbDrone.Core/Download/FailedDownloadService.cs index e15a6b758..0dd698740 100644 --- a/src/NzbDrone.Core/Download/FailedDownloadService.cs +++ b/src/NzbDrone.Core/Download/FailedDownloadService.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using NzbDrone.Common.Extensions; using NzbDrone.Core.Download.TrackedDownloads; @@ -129,7 +129,7 @@ private void PublishDownloadFailedEvent(List historyItems, strin Message = message, Data = historyItem.Data, TrackedDownload = trackedDownload, - Language = historyItem.Language + Languages = historyItem.Languages }; _eventAggregator.PublishEvent(downloadFailedEvent); diff --git a/src/NzbDrone.Core/Download/IgnoredDownloadService.cs b/src/NzbDrone.Core/Download/IgnoredDownloadService.cs index b7d73c1b8..03acc180c 100644 --- a/src/NzbDrone.Core/Download/IgnoredDownloadService.cs +++ b/src/NzbDrone.Core/Download/IgnoredDownloadService.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using NLog; using NzbDrone.Common.Extensions; using NzbDrone.Core.Download.TrackedDownloads; @@ -39,7 +39,7 @@ public bool IgnoreDownload(TrackedDownload trackedDownload) { SeriesId = series.Id, EpisodeIds = episodes.Select(e => e.Id).ToList(), - Language = trackedDownload.RemoteEpisode.ParsedEpisodeInfo.Language, + Languages = trackedDownload.RemoteEpisode.ParsedEpisodeInfo.Languages, Quality = trackedDownload.RemoteEpisode.ParsedEpisodeInfo.Quality, SourceTitle = trackedDownload.DownloadItem.Title, DownloadClientInfo = trackedDownload.DownloadItem.DownloadClientInfo, diff --git a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs index 6e20985dc..0a971b974 100644 --- a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs +++ b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs @@ -180,7 +180,7 @@ public List GetPendingRemoteEpisodes(int seriesId) Id = GetQueueId(pendingRelease, episode), Series = pendingRelease.RemoteEpisode.Series, Episode = episode, - Language = pendingRelease.RemoteEpisode.ParsedEpisodeInfo.Language, + Languages = pendingRelease.RemoteEpisode.ParsedEpisodeInfo.Languages, Quality = pendingRelease.RemoteEpisode.ParsedEpisodeInfo.Quality, Title = pendingRelease.Title, Size = pendingRelease.RemoteEpisode.Release.Size, diff --git a/src/NzbDrone.Core/History/EpisodeHistory.cs b/src/NzbDrone.Core/History/EpisodeHistory.cs index d3fa1d9b5..3b3635f70 100644 --- a/src/NzbDrone.Core/History/EpisodeHistory.cs +++ b/src/NzbDrone.Core/History/EpisodeHistory.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using NzbDrone.Core.Datastore; using NzbDrone.Core.Languages; @@ -26,7 +26,7 @@ public EpisodeHistory() public Series Series { get; set; } public EpisodeHistoryEventType EventType { get; set; } public Dictionary Data { get; set; } - public Language Language { get; set; } + public List Languages { get; set; } public string DownloadId { get; set; } } diff --git a/src/NzbDrone.Core/History/HistoryService.cs b/src/NzbDrone.Core/History/HistoryService.cs index f7b22e712..d87083b78 100644 --- a/src/NzbDrone.Core/History/HistoryService.cs +++ b/src/NzbDrone.Core/History/HistoryService.cs @@ -148,7 +148,7 @@ public void Handle(EpisodeGrabbedEvent message) SeriesId = episode.SeriesId, EpisodeId = episode.Id, DownloadId = message.DownloadId, - Language = message.Episode.ParsedEpisodeInfo.Language, + Languages = message.Episode.ParsedEpisodeInfo.Languages, }; history.Data.Add("Indexer", message.Episode.Release.Indexer); @@ -210,7 +210,7 @@ public void Handle(EpisodeImportedEvent message) SeriesId = message.ImportedEpisode.SeriesId, EpisodeId = episode.Id, DownloadId = downloadId, - Language = message.EpisodeInfo.Language + Languages = message.EpisodeInfo.Languages }; history.Data.Add("FileId", message.ImportedEpisode.Id.ToString()); @@ -237,7 +237,7 @@ public void Handle(DownloadFailedEvent message) SeriesId = message.SeriesId, EpisodeId = episodeId, DownloadId = message.DownloadId, - Language = message.Language + Languages = message.Languages }; history.Data.Add("DownloadClient", message.DownloadClient); @@ -272,7 +272,7 @@ public void Handle(EpisodeFileDeletedEvent message) SourceTitle = message.EpisodeFile.Path, SeriesId = message.EpisodeFile.SeriesId, EpisodeId = episode.Id, - Language = message.EpisodeFile.Language + Languages = message.EpisodeFile.Languages }; history.Data.Add("Reason", message.Reason.ToString()); @@ -299,7 +299,7 @@ public void Handle(EpisodeFileRenamedEvent message) SourceTitle = message.OriginalPath, SeriesId = message.EpisodeFile.SeriesId, EpisodeId = episode.Id, - Language = message.EpisodeFile.Language + Languages = message.EpisodeFile.Languages }; history.Data.Add("SourcePath", sourcePath); @@ -327,7 +327,7 @@ public void Handle(DownloadIgnoredEvent message) SeriesId = message.SeriesId, EpisodeId = episodeId, DownloadId = message.DownloadId, - Language = message.Language + Languages = message.Languages }; history.Data.Add("DownloadClient", message.DownloadClientInfo.Type); diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/EnsureValidLanguageProfileId.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/EnsureValidLanguageProfileId.cs deleted file mode 100644 index 25104a769..000000000 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/EnsureValidLanguageProfileId.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.Profiles.Languages; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Core.Housekeeping.Housekeepers -{ - // For some unknown reason series added through the v2 API can be added without a lanuage profile ID, which breaks things later. - // This ensures there is a language profile ID and it's valid as a safety net. - - public class EnsureValidLanguageProfileId : IHousekeepingTask - { - private readonly ISeriesRepository _seriesRepository; - private readonly ILanguageProfileService _languageProfileService; - - public EnsureValidLanguageProfileId(ISeriesRepository seriesRepository, ILanguageProfileService languageProfileService) - { - _seriesRepository = seriesRepository; - _languageProfileService = languageProfileService; - } - - public void Clean() - { - var languageProfiles = _languageProfileService.All(); - var firstLangaugeProfile = languageProfiles.First(); - var series = _seriesRepository.All().ToList(); - var seriesToUpdate = new List(); - - series.ForEach(s => - { - if (s.LanguageProfileId == 0 || languageProfiles.None(l => l.Id == s.LanguageProfileId)) - { - s.LanguageProfileId = firstLangaugeProfile.Id; - seriesToUpdate.Add(s); - } - }); - - _seriesRepository.UpdateMany(seriesToUpdate); - } - } -} diff --git a/src/NzbDrone.Core/ImportLists/ImportListDefinition.cs b/src/NzbDrone.Core/ImportLists/ImportListDefinition.cs index ae746d5c4..0d38d3fea 100644 --- a/src/NzbDrone.Core/ImportLists/ImportListDefinition.cs +++ b/src/NzbDrone.Core/ImportLists/ImportListDefinition.cs @@ -8,7 +8,6 @@ public class ImportListDefinition : ProviderDefinition public bool EnableAutomaticAdd { get; set; } public MonitorTypes ShouldMonitor { get; set; } public int QualityProfileId { get; set; } - public int LanguageProfileId { get; set; } public SeriesTypes SeriesType { get; set; } public bool SeasonFolder { get; set; } public string RootFolderPath { get; set; } diff --git a/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs b/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs index 3a0cef217..314709f96 100644 --- a/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs +++ b/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs @@ -123,7 +123,6 @@ private void ProcessReports(List reports) Monitored = monitored, RootFolderPath = importList.RootFolderPath, QualityProfileId = importList.QualityProfileId, - LanguageProfileId = importList.LanguageProfileId, SeriesType = importList.SeriesType, SeasonFolder = importList.SeasonFolder, Tags = importList.Tags, diff --git a/src/NzbDrone.Core/Languages/LanguageComparer.cs b/src/NzbDrone.Core/Languages/LanguageComparer.cs deleted file mode 100644 index e286aa681..000000000 --- a/src/NzbDrone.Core/Languages/LanguageComparer.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using NzbDrone.Common.EnsureThat; -using NzbDrone.Core.Profiles.Languages; - -namespace NzbDrone.Core.Languages -{ - public class LanguageComparer : IComparer - { - private readonly LanguageProfile _profile; - - public LanguageComparer(LanguageProfile profile) - { - Ensure.That(profile, () => profile).IsNotNull(); - Ensure.That(profile.Languages, () => profile.Languages).HasItems(); - - _profile = profile; - } - - public int Compare(Language left, Language right) - { - int leftIndex = _profile.Languages.FindIndex(v => v.Language == left); - int rightIndex = _profile.Languages.FindIndex(v => v.Language == right); - - return leftIndex.CompareTo(rightIndex); - } - } -} diff --git a/src/NzbDrone.Core/Languages/LanguageFieldConverter.cs b/src/NzbDrone.Core/Languages/LanguageFieldConverter.cs new file mode 100644 index 000000000..530b5e137 --- /dev/null +++ b/src/NzbDrone.Core/Languages/LanguageFieldConverter.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Core.Annotations; + +namespace NzbDrone.Core.Languages +{ + public class LanguageFieldConverter : ISelectOptionsConverter + { + public List GetSelectOptions() + { + return Language.All + .OrderBy(l => l.Id > 0).ThenBy(l => l.Name) + .ToList() + .ConvertAll(v => new SelectOption { Value = v.Id, Name = v.Name }); + } + } +} diff --git a/src/NzbDrone.Core/Languages/LanguagesComparer.cs b/src/NzbDrone.Core/Languages/LanguagesComparer.cs new file mode 100644 index 000000000..02a9dd3d6 --- /dev/null +++ b/src/NzbDrone.Core/Languages/LanguagesComparer.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Linq; + +namespace NzbDrone.Core.Languages +{ + public class LanguagesComparer : IComparer> + { + public int Compare(List x, List y) + { + if (!x.Any() && !y.Any()) + { + return 0; + } + + if (!x.Any() && y.Any()) + { + return 1; + } + + if (x.Any() && !y.Any()) + { + return -1; + } + + if (x.Count > 1 && y.Count > 1 && x.Count > y.Count) + { + return 1; + } + + if (x.Count > 1 && y.Count > 1 && x.Count < y.Count) + { + return -1; + } + + if (x.Count > 1 && y.Count == 1) + { + return 1; + } + + if (x.Count == 1 && y.Count > 1) + { + return -1; + } + + if (x.Count == 1 && y.Count == 1) + { + return x.First().Name.CompareTo(y.First().Name); + } + + return 0; + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeFile.cs b/src/NzbDrone.Core/MediaFiles/EpisodeFile.cs index aca13e0b0..df77d28d0 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeFile.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeFile.cs @@ -24,7 +24,7 @@ public class EpisodeFile : ModelBase public MediaInfoModel MediaInfo { get; set; } public LazyLoaded> Episodes { get; set; } public LazyLoaded Series { get; set; } - public Language Language { get; set; } + public List Languages { get; set; } public override string ToString() { diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateLanguage.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateLanguage.cs index d861574f4..06b96e87c 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateLanguage.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateLanguage.cs @@ -1,71 +1,55 @@ using System.Collections.Generic; using System.Linq; using NLog; -using NzbDrone.Common.Extensions; using NzbDrone.Core.Download; using NzbDrone.Core.Languages; -using NzbDrone.Core.Parser; +using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators.Augmenters.Language; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Tv; namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators { public class AggregateLanguage : IAggregateLocalEpisode { + private readonly List _augmentLanguages; private readonly Logger _logger; - public AggregateLanguage(Logger logger) + public AggregateLanguage(IEnumerable augmentLanguages, + Logger logger) { + _augmentLanguages = augmentLanguages.OrderBy(a => a.Order).ToList(); _logger = logger; } public LocalEpisode Aggregate(LocalEpisode localEpisode, DownloadClientItem downloadClientItem) { - // Get languages in preferred order, download client item, folder and finally file. - // Non-English languages will be preferred later, in the event there is a conflict - // between parsed languages the more preferred item will be used. + var languages = new List { Language.English }; + var languagesConfidence = Confidence.Default; - var languages = new List - { - GetLanguage(localEpisode.DownloadClientEpisodeInfo, localEpisode.Episodes), - GetLanguage(localEpisode.FolderEpisodeInfo, localEpisode.Episodes), - GetLanguage(localEpisode.FileEpisodeInfo, localEpisode.Episodes) - }; - - var language = languages.FirstOrDefault(l => l != Language.English) ?? Language.English; - - _logger.Debug("Using language: {0}", language); - - localEpisode.Language = language; - - return localEpisode; - } - - private Language GetLanguage(ParsedEpisodeInfo parsedEpisodeInfo, List episodes) - { - if (parsedEpisodeInfo == null) + foreach (var augmentLanguage in _augmentLanguages) { - return Language.English; - } - - var normalizedReleaseTitle = Parser.Parser.NormalizeEpisodeTitle(parsedEpisodeInfo.ReleaseTitle); - - foreach (var episode in episodes) - { - var episodeTitleLanguage = LanguageParser.ParseLanguage(episode.Title, false); - - if (episodeTitleLanguage != Language.Unknown && episodeTitleLanguage == parsedEpisodeInfo.Language) + var augmentedLanguage = augmentLanguage.AugmentLanguage(localEpisode, downloadClientItem); + if (augmentedLanguage == null) { - // Release title contains the episode title, return english instead of the parsed language. + continue; + } - if (normalizedReleaseTitle.ContainsIgnoreCase(Parser.Parser.NormalizeEpisodeTitle(episode.Title))) - { - return Language.English; - } + _logger.Trace("Considering Languages {0} ({1}) from {2}", string.Join(", ", augmentedLanguage.Languages ?? new List()), augmentedLanguage.Confidence, augmentLanguage.Name); + + if (augmentedLanguage?.Languages != null && + augmentedLanguage.Languages.Count > 0 && + !(augmentedLanguage.Languages.Count == 1 && augmentedLanguage.Languages.Contains(Language.English)) && + !(augmentedLanguage.Languages.Count == 1 && augmentedLanguage.Languages.Contains(Language.Unknown))) + { + languages = augmentedLanguage.Languages; + languagesConfidence = augmentedLanguage.Confidence; } } - return parsedEpisodeInfo.Language; + _logger.Debug("Selected languages: {0}", string.Join(", ", languages.ToList())); + + localEpisode.Languages = languages; + + return localEpisode; } } } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromDownloadClientItem.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromDownloadClientItem.cs new file mode 100644 index 000000000..3a5260dc6 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromDownloadClientItem.cs @@ -0,0 +1,34 @@ +using System.Linq; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Download; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators.Augmenters.Language +{ + public class AugmentLanguageFromDownloadClientItem : IAugmentLanguage + { + public int Order => 3; + public string Name => "DownloadClientItem"; + + public AugmentLanguageResult AugmentLanguage(LocalEpisode localEpisode, DownloadClientItem downloadClientItem) + { + var languages = localEpisode.DownloadClientEpisodeInfo?.Languages; + + if (languages == null) + { + return null; + } + + foreach (var episode in localEpisode.Episodes) + { + var episodeTitleLanguage = LanguageParser.ParseLanguages(episode.Title, false); + + languages = localEpisode.DownloadClientEpisodeInfo.Languages.Except(episodeTitleLanguage).ToList(); + } + + return new AugmentLanguageResult(languages, Confidence.DownloadClientItem); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromFileName.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromFileName.cs new file mode 100644 index 000000000..307f25584 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromFileName.cs @@ -0,0 +1,32 @@ +using System.Linq; +using NzbDrone.Core.Download; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators.Augmenters.Language +{ + public class AugmentLanguageFromFileName : IAugmentLanguage + { + public int Order => 1; + public string Name => "FileName"; + + public AugmentLanguageResult AugmentLanguage(LocalEpisode localEpisode, DownloadClientItem downloadClientItem) + { + var languages = localEpisode.FileEpisodeInfo?.Languages; + + if (languages == null) + { + return null; + } + + foreach (var episode in localEpisode.Episodes) + { + var episodeTitleLanguage = LanguageParser.ParseLanguages(episode.Title, false); + + languages = localEpisode.DownloadClientEpisodeInfo.Languages.Except(episodeTitleLanguage).ToList(); + } + + return new AugmentLanguageResult(languages, Confidence.Filename); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromFolder.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromFolder.cs new file mode 100644 index 000000000..60e378168 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromFolder.cs @@ -0,0 +1,32 @@ +using System.Linq; +using NzbDrone.Core.Download; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators.Augmenters.Language +{ + public class AugmentLanguageFromFolder : IAugmentLanguage + { + public int Order => 2; + public string Name => "FolderName"; + + public AugmentLanguageResult AugmentLanguage(LocalEpisode localEpisode, DownloadClientItem downloadClientItem) + { + var languages = localEpisode.FolderEpisodeInfo?.Languages; + + if (languages == null) + { + return null; + } + + foreach (var episode in localEpisode.Episodes) + { + var episodeTitleLanguage = LanguageParser.ParseLanguages(episode.Title, false); + + languages = localEpisode.DownloadClientEpisodeInfo.Languages.Except(episodeTitleLanguage).ToList(); + } + + return new AugmentLanguageResult(languages, Confidence.Foldername); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromMediaInfo.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromMediaInfo.cs new file mode 100644 index 000000000..c38efcce9 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromMediaInfo.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Download; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators.Augmenters.Language +{ + public class AugmentLanguageFromMediaInfo : IAugmentLanguage + { + public int Order => 4; + public string Name => "MediaInfo"; + + public AugmentLanguageResult AugmentLanguage(LocalEpisode localEpisode, DownloadClientItem downloadClientItem) + { + if (localEpisode.MediaInfo == null) + { + return null; + } + + var audioLanguages = localEpisode.MediaInfo.AudioLanguages.Distinct().ToList(); + + var languages = new List(); + + foreach (var audioLanguage in audioLanguages) + { + var language = IsoLanguages.Find(audioLanguage)?.Language; + languages.AddIfNotNull(language); + } + + if (languages.Count == 0) + { + return null; + } + + return new AugmentLanguageResult(languages, Confidence.MediaInfo); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageResult.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageResult.cs new file mode 100644 index 000000000..2c6606bac --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageResult.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators.Augmenters.Language +{ + public class AugmentLanguageResult + { + public string Name { get; set; } + public List Languages { get; set; } + public Confidence Confidence { get; set; } + + public AugmentLanguageResult(List languages, + Confidence confidence) + { + Languages = languages; + Confidence = confidence; + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/Confidence.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/Confidence.cs new file mode 100644 index 000000000..d218dfa33 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/Confidence.cs @@ -0,0 +1,11 @@ +namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators.Augmenters.Language +{ + public enum Confidence + { + Default, + Filename, + Foldername, + DownloadClientItem, + MediaInfo + } +} diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/IAugmentLanguage.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/IAugmentLanguage.cs new file mode 100644 index 000000000..933d26674 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/Augmenters/Language/IAugmentLanguage.cs @@ -0,0 +1,12 @@ +using NzbDrone.Core.Download; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators.Augmenters.Language +{ + public interface IAugmentLanguage + { + int Order { get; } + string Name { get; } + AugmentLanguageResult AugmentLanguage(LocalEpisode localEpisode, DownloadClientItem downloadClientItem); + } +} diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs index 62fc0e291..1ca0d1cee 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs @@ -49,7 +49,6 @@ public List Import(List decisions, bool newDownloa var qualifiedImports = decisions.Where(c => c.Approved) .GroupBy(c => c.LocalEpisode.Series.Id, (i, s) => s .OrderByDescending(c => c.LocalEpisode.Quality, new QualityModelComparer(s.First().LocalEpisode.Series.QualityProfile)) - .ThenByDescending(c => c.LocalEpisode.Language, new LanguageComparer(s.First().LocalEpisode.Series.LanguageProfile)) .ThenByDescending(c => c.LocalEpisode.Size)) .SelectMany(c => c) .ToList(); @@ -84,7 +83,7 @@ public List Import(List decisions, bool newDownloa episodeFile.SeasonNumber = localEpisode.SeasonNumber; episodeFile.Episodes = localEpisode.Episodes; episodeFile.ReleaseGroup = localEpisode.ReleaseGroup; - episodeFile.Language = localEpisode.Language; + episodeFile.Languages = localEpisode.Languages; bool copyOnly; switch (importMode) diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportFile.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportFile.cs index 8f155568b..a2ec3ef35 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportFile.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportFile.cs @@ -14,7 +14,7 @@ public class ManualImportFile : IEquatable public List EpisodeIds { get; set; } public int? EpisodeFileId { get; set; } public QualityModel Quality { get; set; } - public Language Language { get; set; } + public List Languages { get; set; } public string ReleaseGroup { get; set; } public string DownloadId { get; set; } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportItem.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportItem.cs index 76f84724f..bb251364b 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportItem.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportItem.cs @@ -18,7 +18,7 @@ public class ManualImportItem public List Episodes { get; set; } public int? EpisodeFileId { get; set; } public QualityModel Quality { get; set; } - public Language Language { get; set; } + public List Languages { get; set; } public string ReleaseGroup { get; set; } public string DownloadId { get; set; } public IEnumerable Rejections { get; set; } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs index 5c15189f0..6b6c50871 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs @@ -24,7 +24,7 @@ public interface IManualImportService { List GetMediaFiles(int seriesId, int? seasonNumber); List GetMediaFiles(string path, string downloadId, int? seriesId, bool filterExistingFiles); - ManualImportItem ReprocessItem(string path, string downloadId, int seriesId, int? seasonNumber, List episodeIds, string releaseGroup, QualityModel quality, Language language); + ManualImportItem ReprocessItem(string path, string downloadId, int seriesId, int? seasonNumber, List episodeIds, string releaseGroup, QualityModel quality, List languages); } public class ManualImportService : IExecute, IManualImportService @@ -97,7 +97,7 @@ public List GetMediaFiles(int seriesId, int? seasonNumber) Episodes = new List(), ReleaseGroup = string.Empty, Quality = new QualityModel(Quality.Unknown), - Language = Language.Unknown, + Languages = new List { Language.Unknown }, Size = _diskProvider.GetFileSize(file), Rejections = Enumerable.Empty() })); @@ -134,7 +134,7 @@ public List GetMediaFiles(string path, string downloadId, int? return ProcessFolder(path, path, downloadId, seriesId, filterExistingFiles); } - public ManualImportItem ReprocessItem(string path, string downloadId, int seriesId, int? seasonNumber, List episodeIds, string releaseGroup, QualityModel quality, Language language) + public ManualImportItem ReprocessItem(string path, string downloadId, int seriesId, int? seasonNumber, List episodeIds, string releaseGroup, QualityModel quality, List languages) { var rootFolder = Path.GetDirectoryName(path); var series = _seriesService.GetSeries(seriesId); @@ -153,7 +153,7 @@ public ManualImportItem ReprocessItem(string path, string downloadId, int series localEpisode.ExistingFile = series.Path.IsParentPath(path); localEpisode.Size = _diskProvider.GetFileSize(path); localEpisode.ReleaseGroup = releaseGroup.IsNullOrWhiteSpace() ? Parser.Parser.ParseReleaseGroup(path) : releaseGroup; - localEpisode.Language = language == Language.Unknown ? LanguageParser.ParseLanguage(path) : language; + localEpisode.Languages = (languages?.SingleOrDefault() ?? Language.Unknown) == Language.Unknown ? LanguageParser.ParseLanguages(path) : languages; localEpisode.Quality = quality.Quality == Quality.Unknown ? QualityParser.ParseQuality(path) : quality; return MapItem(_importDecisionMaker.GetDecision(localEpisode, downloadClientItem), rootFolder, downloadId, null); @@ -179,7 +179,7 @@ public ManualImportItem ReprocessItem(string path, string downloadId, int series ExistingFile = series.Path.IsParentPath(path), Size = _diskProvider.GetFileSize(path), ReleaseGroup = releaseGroup.IsNullOrWhiteSpace() ? Parser.Parser.ParseReleaseGroup(path) : releaseGroup, - Language = language == Language.Unknown ? LanguageParser.ParseLanguage(path) : language, + Languages = (languages?.SingleOrDefault() ?? Language.Unknown) == Language.Unknown ? LanguageParser.ParseLanguages(path) : languages, Quality = quality.Quality == Quality.Unknown ? QualityParser.ParseQuality(path) : quality }; @@ -290,7 +290,7 @@ private ManualImportItem ProcessFile(string rootFolder, string baseFolder, strin localEpisode.Path = file; localEpisode.ReleaseGroup = Parser.Parser.ParseReleaseGroup(file); localEpisode.Quality = QualityParser.ParseQuality(file); - localEpisode.Language = LanguageParser.ParseLanguage(file); + localEpisode.Languages = LanguageParser.ParseLanguages(file); localEpisode.Size = _diskProvider.GetFileSize(file); return MapItem(new ImportDecision(localEpisode, @@ -335,7 +335,7 @@ private List ProcessDownloadDirectory(string rootFolder, List< var localEpisode = new LocalEpisode(); localEpisode.Path = file; localEpisode.Quality = new QualityModel(Quality.Unknown); - localEpisode.Language = Language.Unknown; + localEpisode.Languages = new List { Language.Unknown }; localEpisode.Size = _diskProvider.GetFileSize(file); items.Add(MapItem(new ImportDecision(localEpisode), rootFolder, null, null)); @@ -397,7 +397,7 @@ private ManualImportItem MapItem(ImportDecision decision, string rootFolder, str item.ReleaseGroup = decision.LocalEpisode.ReleaseGroup; item.Quality = decision.LocalEpisode.Quality; - item.Language = decision.LocalEpisode.Language; + item.Languages = decision.LocalEpisode.Languages; item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path); item.Rejections = decision.Rejections; @@ -417,7 +417,7 @@ private ManualImportItem MapItem(EpisodeFile episodeFile, Series series, string item.Episodes = episodeFile.Episodes.Value; item.ReleaseGroup = episodeFile.ReleaseGroup; item.Quality = episodeFile.Quality; - item.Language = episodeFile.Language; + item.Languages = episodeFile.Languages; item.Size = _diskProvider.GetFileSize(item.Path); item.Rejections = Enumerable.Empty(); item.EpisodeFileId = episodeFile.Id; @@ -452,7 +452,7 @@ public void Execute(ManualImportCommand message) Path = file.Path, ReleaseGroup = file.ReleaseGroup, Quality = file.Quality, - Language = file.Language, + Languages = file.Languages, Series = series, Size = 0 }; @@ -477,7 +477,7 @@ public void Execute(ManualImportCommand message) localEpisode.Episodes = episodes; localEpisode.ReleaseGroup = file.ReleaseGroup; localEpisode.Quality = file.Quality; - localEpisode.Language = file.Language; + localEpisode.Languages = file.Languages; //TODO: Cleanup non-tracked downloads diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs index a702a3312..ae7f5d9af 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs @@ -31,7 +31,6 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down { var downloadPropersAndRepacks = _configService.DownloadPropersAndRepacks; var qualityComparer = new QualityModelComparer(localEpisode.Series.QualityProfile); - var languageComparer = new LanguageComparer(localEpisode.Series.LanguageProfile); foreach (var episode in localEpisode.Episodes.Where(e => e.EpisodeFileId > 0)) { @@ -44,7 +43,6 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down } var qualityCompare = qualityComparer.Compare(localEpisode.Quality.Quality, episodeFile.Quality.Quality); - var languageCompare = languageComparer.Compare(localEpisode.Language, episodeFile.Language); var customFormatScore = GetCustomFormatScore(localEpisode); if (qualityCompare < 0) @@ -58,7 +56,6 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down // they just don't import automatically. if (qualityCompare == 0 && - languageCompare <= 0 && downloadPropersAndRepacks != ProperDownloadTypes.DoNotPrefer && localEpisode.Quality.Revision.CompareTo(episodeFile.Quality.Revision) < 0) { @@ -66,12 +63,6 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down return Decision.Reject("Not a quality revision upgrade for existing episode file(s)"); } - if (languageCompare < 0 && qualityCompare == 0) - { - _logger.Debug("This file isn't a language upgrade for all episodes. Skipping {0}", localEpisode.Path); - return Decision.Reject("Not a language upgrade for existing episode file(s)"); - } - var customFormats = _customFormatCalculationService.ParseCustomFormat(episodeFile); var episodeFileCustomFormatScore = localEpisode.Series.QualityProfile.Value.CalculateCustomFormatScore(customFormats); diff --git a/src/NzbDrone.Core/Parser/LanguageParser.cs b/src/NzbDrone.Core/Parser/LanguageParser.cs index c2b62ab4e..a2d89dfd5 100644 --- a/src/NzbDrone.Core/Parser/LanguageParser.cs +++ b/src/NzbDrone.Core/Parser/LanguageParser.cs @@ -27,7 +27,7 @@ public static class LanguageParser private static readonly Regex SubtitleLanguageRegex = new Regex(".+?[-_. ](?[a-z]{2,3})([-_. ](?full|forced|foreign|default|cc|psdh|sdh))*$", RegexOptions.Compiled | RegexOptions.IgnoreCase); - public static Language ParseLanguage(string title, bool defaultToEnglish = true) + public static List ParseLanguages(string title, bool defaultToEnglish = true) { foreach (var regex in CleanSeriesTitleRegex) { @@ -39,134 +39,141 @@ public static Language ParseLanguage(string title, bool defaultToEnglish = true) var lowerTitle = title.ToLower(); + var languages = new List(); + if (lowerTitle.Contains("french")) { - return Language.French; + languages.Add(Language.French); } if (lowerTitle.Contains("spanish")) { - return Language.Spanish; + languages.Add(Language.Spanish); } if (lowerTitle.Contains("danish")) { - return Language.Danish; + languages.Add(Language.Danish); } if (lowerTitle.Contains("dutch")) { - return Language.Dutch; + languages.Add(Language.Dutch); } if (lowerTitle.Contains("japanese")) { - return Language.Japanese; + languages.Add(Language.Japanese); } if (lowerTitle.Contains("icelandic")) { - return Language.Icelandic; + languages.Add(Language.Icelandic); } if (lowerTitle.Contains("mandarin") || lowerTitle.Contains("cantonese") || lowerTitle.Contains("chinese")) { - return Language.Chinese; + languages.Add(Language.Chinese); } if (lowerTitle.Contains("korean")) { - return Language.Korean; + languages.Add(Language.Korean); } if (lowerTitle.Contains("russian")) { - return Language.Russian; + languages.Add(Language.Russian); } if (lowerTitle.Contains("polish")) { - return Language.Polish; + languages.Add(Language.Polish); } if (lowerTitle.Contains("vietnamese")) { - return Language.Vietnamese; + languages.Add(Language.Vietnamese); } if (lowerTitle.Contains("swedish")) { - return Language.Swedish; + languages.Add(Language.Swedish); } if (lowerTitle.Contains("norwegian")) { - return Language.Norwegian; + languages.Add(Language.Norwegian); } if (lowerTitle.Contains("finnish")) { - return Language.Finnish; + languages.Add(Language.Finnish); } if (lowerTitle.Contains("turkish")) { - return Language.Turkish; + languages.Add(Language.Turkish); } if (lowerTitle.Contains("portuguese")) { - return Language.Portuguese; + languages.Add(Language.Portuguese); } if (lowerTitle.Contains("hungarian")) { - return Language.Hungarian; + languages.Add(Language.Hungarian); } if (lowerTitle.Contains("hebrew")) { - return Language.Hebrew; + languages.Add(Language.Hebrew); } if (lowerTitle.Contains("arabic")) { - return Language.Arabic; + languages.Add(Language.Arabic); } if (lowerTitle.Contains("hindi")) { - return Language.Hindi; + languages.Add(Language.Hindi); } if (lowerTitle.Contains("malayalam")) { - return Language.Malayalam; + languages.Add(Language.Malayalam); } if (lowerTitle.Contains("ukrainian")) { - return Language.Ukrainian; + languages.Add(Language.Ukrainian); } if (lowerTitle.Contains("bulgarian")) { - return Language.Bulgarian; + languages.Add(Language.Bulgarian); } - var regexLanguage = RegexLanguage(title); + var regexLanguages = RegexLanguage(title); - if (regexLanguage != Language.Unknown) + if (regexLanguages.Any()) { - return regexLanguage; + languages.AddRange(regexLanguages); } if (lowerTitle.Contains("english")) { - return Language.English; + languages.Add(Language.English); } - return defaultToEnglish ? Language.English : Language.Unknown; + if (!languages.Any()) + { + languages.Add(defaultToEnglish ? Language.English : Language.Unknown); + } + + return languages.DistinctBy(l => (int)l).ToList(); } public static Language ParseSubtitleLanguage(string fileName) @@ -223,29 +230,31 @@ public static IEnumerable ParseLanguageTags(string fileName) return Enumerable.Empty(); } - private static Language RegexLanguage(string title) + private static List RegexLanguage(string title) { + var languages = new List(); + // Case sensitive var caseSensitiveMatch = CaseSensitiveLanguageRegex.Match(title); if (caseSensitiveMatch.Groups["lithuanian"].Captures.Cast().Any()) { - return Language.Lithuanian; + languages.Add(Language.Lithuanian); } if (caseSensitiveMatch.Groups["czech"].Captures.Cast().Any()) { - return Language.Czech; + languages.Add(Language.Czech); } if (caseSensitiveMatch.Groups["polish"].Captures.Cast().Any()) { - return Language.Polish; + languages.Add(Language.Polish); } if (caseSensitiveMatch.Groups["bulgarian"].Captures.Cast().Any()) { - return Language.Bulgarian; + languages.Add(Language.Bulgarian); } // Case insensitive @@ -253,75 +262,75 @@ private static Language RegexLanguage(string title) if (match.Groups["italian"].Captures.Cast().Any()) { - return Language.Italian; + languages.Add(Language.Italian); } if (match.Groups["german"].Captures.Cast().Any()) { - return Language.German; + languages.Add(Language.German); } if (match.Groups["flemish"].Captures.Cast().Any()) { - return Language.Flemish; + languages.Add(Language.Flemish); } if (match.Groups["greek"].Captures.Cast().Any()) { - return Language.Greek; + languages.Add(Language.Greek); } if (match.Groups["french"].Success) { - return Language.French; + languages.Add(Language.French); } if (match.Groups["russian"].Success) { - return Language.Russian; + languages.Add(Language.Russian); } if (match.Groups["dutch"].Success) { - return Language.Dutch; + languages.Add(Language.Dutch); } if (match.Groups["hungarian"].Success) { - return Language.Hungarian; + languages.Add(Language.Hungarian); } if (match.Groups["hebrew"].Success) { - return Language.Hebrew; + languages.Add(Language.Hebrew); } if (match.Groups["polish"].Success) { - return Language.Polish; + languages.Add(Language.Polish); } if (match.Groups["chinese"].Success) { - return Language.Chinese; + languages.Add(Language.Chinese); } if (match.Groups["bulgarian"].Success) { - return Language.Bulgarian; + languages.Add(Language.Bulgarian); } if (match.Groups["ukrainian"].Success) { - return Language.Ukrainian; + languages.Add(Language.Ukrainian); } if (match.Groups["spanish"].Success) { - return Language.Spanish; + languages.Add(Language.Spanish); } - return Language.Unknown; + return languages; } } } diff --git a/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs b/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs index efc0d91d4..e233cdcf6 100644 --- a/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs +++ b/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs @@ -13,6 +13,7 @@ public class LocalEpisode public LocalEpisode() { Episodes = new List(); + Languages = new List(); } public string Path { get; set; } @@ -23,7 +24,7 @@ public LocalEpisode() public Series Series { get; set; } public List Episodes { get; set; } public QualityModel Quality { get; set; } - public Language Language { get; set; } + public List Languages { get; set; } public MediaInfoModel MediaInfo { get; set; } public bool ExistingFile { get; set; } public bool SceneSource { get; set; } diff --git a/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs b/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs index 24b6bbec7..b4f0e3100 100644 --- a/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs +++ b/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs @@ -18,7 +18,7 @@ public class ParsedEpisodeInfo public int[] AbsoluteEpisodeNumbers { get; set; } public decimal[] SpecialAbsoluteEpisodeNumbers { get; set; } public string AirDate { get; set; } - public Language Language { get; set; } + public List Languages { get; set; } public bool FullSeason { get; set; } public bool IsPartialSeason { get; set; } public bool IsMultiSeason { get; set; } @@ -38,6 +38,7 @@ public ParsedEpisodeInfo() EpisodeNumbers = new int[0]; AbsoluteEpisodeNumbers = new int[0]; SpecialAbsoluteEpisodeNumbers = new decimal[0]; + Languages = new List(); } public bool IsDaily diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index dacb2dc3f..dafa782de 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -622,8 +622,8 @@ public static ParsedEpisodeInfo ParseTitle(string title) result.Special = true; } - result.Language = LanguageParser.ParseLanguage(releaseTitle); - Logger.Debug("Language parsed: {0}", result.Language); + result.Languages = LanguageParser.ParseLanguages(releaseTitle); + Logger.Debug("Languages parsed: {0}", string.Join(", ", result.Languages)); result.Quality = QualityParser.ParseQuality(title); Logger.Debug("Quality parsed: {0}", result.Quality); diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs index d5f6a9b4f..bbdc76156 100644 --- a/src/NzbDrone.Core/Parser/ParsingService.cs +++ b/src/NzbDrone.Core/Parser/ParsingService.cs @@ -330,7 +330,7 @@ public ParsedEpisodeInfo ParseSpecialEpisodeTitle(ParsedEpisodeInfo parsedEpisod FullSeason = false, Quality = QualityParser.ParseQuality(releaseTitle), ReleaseGroup = Parser.ParseReleaseGroup(releaseTitle), - Language = LanguageParser.ParseLanguage(releaseTitle), + Languages = LanguageParser.ParseLanguages(releaseTitle), Special = true }; diff --git a/src/NzbDrone.Core/Profiles/Languages/LanguageProfile.cs b/src/NzbDrone.Core/Profiles/Languages/LanguageProfile.cs deleted file mode 100644 index feece2da2..000000000 --- a/src/NzbDrone.Core/Profiles/Languages/LanguageProfile.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using NzbDrone.Core.Datastore; -using NzbDrone.Core.Languages; - -namespace NzbDrone.Core.Profiles.Languages -{ - public class LanguageProfile : ModelBase - { - public string Name { get; set; } - public List Languages { get; set; } - public bool UpgradeAllowed { get; set; } - public Language Cutoff { get; set; } - - public Language FirstAllowedLanguage() - { - return Languages.First(q => q.Allowed).Language; - } - - public Language LastAllowedLanguage() - { - return Languages.Last(q => q.Allowed).Language; - } - } -} diff --git a/src/NzbDrone.Core/Profiles/Languages/LanguageProfileInUseException.cs b/src/NzbDrone.Core/Profiles/Languages/LanguageProfileInUseException.cs deleted file mode 100644 index ba8c0e93d..000000000 --- a/src/NzbDrone.Core/Profiles/Languages/LanguageProfileInUseException.cs +++ /dev/null @@ -1,12 +0,0 @@ -using NzbDrone.Common.Exceptions; - -namespace NzbDrone.Core.Profiles.Languages -{ - public class LanguageProfileInUseException : NzbDroneException - { - public LanguageProfileInUseException(int profileId) - : base("Language profile [{0}] is in use.", profileId) - { - } - } -} diff --git a/src/NzbDrone.Core/Profiles/Languages/LanguageProfileItem.cs b/src/NzbDrone.Core/Profiles/Languages/LanguageProfileItem.cs deleted file mode 100644 index 9ef49508a..000000000 --- a/src/NzbDrone.Core/Profiles/Languages/LanguageProfileItem.cs +++ /dev/null @@ -1,11 +0,0 @@ -using NzbDrone.Core.Datastore; -using NzbDrone.Core.Languages; - -namespace NzbDrone.Core.Profiles.Languages -{ - public class LanguageProfileItem : IEmbeddedDocument - { - public Language Language { get; set; } - public bool Allowed { get; set; } - } -} diff --git a/src/NzbDrone.Core/Profiles/Languages/LanguageProfileRepository.cs b/src/NzbDrone.Core/Profiles/Languages/LanguageProfileRepository.cs deleted file mode 100644 index 9bb44d597..000000000 --- a/src/NzbDrone.Core/Profiles/Languages/LanguageProfileRepository.cs +++ /dev/null @@ -1,23 +0,0 @@ -using NzbDrone.Core.Datastore; -using NzbDrone.Core.Messaging.Events; - -namespace NzbDrone.Core.Profiles.Languages -{ - public interface ILanguageProfileRepository : IBasicRepository - { - bool Exists(int id); - } - - public class LanguageProfileRepository : BasicRepository, ILanguageProfileRepository - { - public LanguageProfileRepository(IMainDatabase database, IEventAggregator eventAggregator) - : base(database, eventAggregator) - { - } - - public bool Exists(int id) - { - return Query(p => p.Id == id).Count == 1; - } - } -} diff --git a/src/NzbDrone.Core/Profiles/Languages/LanguageProfileService.cs b/src/NzbDrone.Core/Profiles/Languages/LanguageProfileService.cs deleted file mode 100644 index 0d5303f6c..000000000 --- a/src/NzbDrone.Core/Profiles/Languages/LanguageProfileService.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using NLog; -using NzbDrone.Core.ImportLists; -using NzbDrone.Core.Languages; -using NzbDrone.Core.Lifecycle; -using NzbDrone.Core.Messaging.Events; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Core.Profiles.Languages -{ - public interface ILanguageProfileService - { - LanguageProfile Add(LanguageProfile profile); - void Update(LanguageProfile profile); - void Delete(int id); - List All(); - LanguageProfile Get(int id); - bool Exists(int id); - LanguageProfile GetDefaultProfile(string name, Language cutoff = null, params Language[] allowed); - } - - public class LanguageProfileService : ILanguageProfileService, IHandle - { - private readonly ILanguageProfileRepository _profileRepository; - private readonly IImportListFactory _importListFactory; - private readonly ISeriesService _seriesService; - private readonly Logger _logger; - - public LanguageProfileService(ILanguageProfileRepository profileRepository, IImportListFactory importListFactory, ISeriesService seriesService, Logger logger) - { - _profileRepository = profileRepository; - _importListFactory = importListFactory; - _seriesService = seriesService; - _logger = logger; - } - - public LanguageProfile Add(LanguageProfile profile) - { - return _profileRepository.Insert(profile); - } - - public void Update(LanguageProfile profile) - { - _profileRepository.Update(profile); - } - - public void Delete(int id) - { - if (_seriesService.GetAllSeries().Any(c => c.LanguageProfileId == id) || _importListFactory.All().Any(c => c.LanguageProfileId == id)) - { - throw new LanguageProfileInUseException(id); - } - - _profileRepository.Delete(id); - } - - public List All() - { - return _profileRepository.All().ToList(); - } - - public LanguageProfile Get(int id) - { - return _profileRepository.Get(id); - } - - public bool Exists(int id) - { - return _profileRepository.Exists(id); - } - - public LanguageProfile GetDefaultProfile(string name, Language cutoff = null, params Language[] allowed) - { - var orderedLanguages = Language.All - .Where(l => l != Language.Unknown) - .OrderByDescending(l => l.Name) - .ToList(); - - orderedLanguages.Insert(0, Language.Unknown); - - var languages = orderedLanguages.Select(v => new LanguageProfileItem { Language = v, Allowed = false }) - .ToList(); - - return new LanguageProfile - { - Cutoff = Language.Unknown, - Languages = languages - }; - } - - private LanguageProfile AddDefaultProfile(string name, Language cutoff, params Language[] allowed) - { - var languages = Language.All - .OrderByDescending(l => l.Name) - .Select(v => new LanguageProfileItem { Language = v, Allowed = allowed.Contains(v) }) - .ToList(); - - var profile = new LanguageProfile - { - Name = name, - Cutoff = cutoff, - Languages = languages, - }; - - return Add(profile); - } - - public void Handle(ApplicationStartedEvent message) - { - if (All().Any()) - { - return; - } - - _logger.Info("Setting up default language profiles"); - - AddDefaultProfile("English", Language.English, Language.English); - } - } -} diff --git a/src/NzbDrone.Core/Queue/Queue.cs b/src/NzbDrone.Core/Queue/Queue.cs index 48bd27b4a..da94f8911 100644 --- a/src/NzbDrone.Core/Queue/Queue.cs +++ b/src/NzbDrone.Core/Queue/Queue.cs @@ -14,7 +14,7 @@ public class Queue : ModelBase { public Series Series { get; set; } public Episode Episode { get; set; } - public Language Language { get; set; } + public List Languages { get; set; } public QualityModel Quality { get; set; } public decimal Size { get; set; } public string Title { get; set; } diff --git a/src/NzbDrone.Core/Queue/QueueService.cs b/src/NzbDrone.Core/Queue/QueueService.cs index 49d3be04b..18157d78b 100644 --- a/src/NzbDrone.Core/Queue/QueueService.cs +++ b/src/NzbDrone.Core/Queue/QueueService.cs @@ -63,7 +63,7 @@ private Queue MapQueueItem(TrackedDownload trackedDownload, Episode episode) { Series = trackedDownload.RemoteEpisode?.Series, Episode = episode, - Language = trackedDownload.RemoteEpisode?.ParsedEpisodeInfo.Language ?? Language.Unknown, + Languages = trackedDownload.RemoteEpisode?.ParsedEpisodeInfo.Languages ?? new List { Language.Unknown }, Quality = trackedDownload.RemoteEpisode?.ParsedEpisodeInfo.Quality ?? new QualityModel(Quality.Unknown), Title = Parser.Parser.RemoveFileExtension(trackedDownload.DownloadItem.Title), Size = trackedDownload.DownloadItem.TotalSize, diff --git a/src/NzbDrone.Core/Tv/EpisodeCutoffService.cs b/src/NzbDrone.Core/Tv/EpisodeCutoffService.cs index 29d298dd0..dcf3c71cb 100644 --- a/src/NzbDrone.Core/Tv/EpisodeCutoffService.cs +++ b/src/NzbDrone.Core/Tv/EpisodeCutoffService.cs @@ -1,9 +1,8 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using NLog; using NzbDrone.Core.Datastore; using NzbDrone.Core.Languages; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; @@ -18,13 +17,11 @@ public class EpisodeCutoffService : IEpisodeCutoffService { private readonly IEpisodeRepository _episodeRepository; private readonly IQualityProfileService _qualityProfileService; - private readonly ILanguageProfileService _languageProfileService; - public EpisodeCutoffService(IEpisodeRepository episodeRepository, IQualityProfileService qualityProfileService, ILanguageProfileService languageProfileService, Logger logger) + public EpisodeCutoffService(IEpisodeRepository episodeRepository, IQualityProfileService qualityProfileService, Logger logger) { _episodeRepository = episodeRepository; _qualityProfileService = qualityProfileService; - _languageProfileService = languageProfileService; } public PagingSpec EpisodesWhereCutoffUnmet(PagingSpec pagingSpec) @@ -32,7 +29,6 @@ public PagingSpec EpisodesWhereCutoffUnmet(PagingSpec pagingSp var qualitiesBelowCutoff = new List(); var languagesBelowCutoff = new List(); var profiles = _qualityProfileService.All(); - var languageProfiles = _languageProfileService.All(); //Get all items less than the cutoff foreach (var profile in profiles) @@ -47,18 +43,6 @@ public PagingSpec EpisodesWhereCutoffUnmet(PagingSpec pagingSp } } - foreach (var profile in languageProfiles) - { - var languageCutoff = profile.UpgradeAllowed ? profile.Cutoff : profile.FirstAllowedLanguage(); - var languageCutoffIndex = profile.Languages.FindIndex(v => v.Language == languageCutoff); - var belowLanguageCutoff = profile.Languages.Take(languageCutoffIndex).ToList(); - - if (belowLanguageCutoff.Any()) - { - languagesBelowCutoff.Add(new LanguagesBelowCutoff(profile.Id, belowLanguageCutoff.Select(l => l.Language.Id))); - } - } - return _episodeRepository.EpisodesWhereCutoffUnmet(pagingSpec, qualitiesBelowCutoff, languagesBelowCutoff, false); } } diff --git a/src/NzbDrone.Core/Tv/EpisodeRepository.cs b/src/NzbDrone.Core/Tv/EpisodeRepository.cs index cddcb1bb4..7c3ecfd44 100644 --- a/src/NzbDrone.Core/Tv/EpisodeRepository.cs +++ b/src/NzbDrone.Core/Tv/EpisodeRepository.cs @@ -219,9 +219,8 @@ private SqlBuilder EpisodesWhereCutoffUnmetBuilder(List qu .Where(e => e.EpisodeFileId != 0) .Where(e => e.SeasonNumber >= startingSeasonNumber) .Where( - string.Format("({0} OR {1})", - BuildQualityCutoffWhereClause(qualitiesBelowCutoff), - BuildLanguageCutoffWhereClause(languagesBelowCutoff))) + string.Format("({0})", + BuildQualityCutoffWhereClause(qualitiesBelowCutoff))) .GroupBy(e => e.Id); private string BuildQualityCutoffWhereClause(List qualitiesBelowCutoff) @@ -239,21 +238,6 @@ private string BuildQualityCutoffWhereClause(List qualitie return string.Format("({0})", string.Join(" OR ", clauses)); } - private string BuildLanguageCutoffWhereClause(List languagesBelowCutoff) - { - var clauses = new List(); - - foreach (var profile in languagesBelowCutoff) - { - foreach (var belowCutoff in profile.LanguageIds) - { - clauses.Add(string.Format("(Series.[LanguageProfileId] = {0} AND EpisodeFiles.Language = {1})", profile.ProfileId, belowCutoff)); - } - } - - return string.Format("({0})", string.Join(" OR ", clauses)); - } - private Episode FindOneByAirDate(int seriesId, string date) { var episodes = Query(s => s.SeriesId == seriesId && s.AirDate == date).ToList(); diff --git a/src/NzbDrone.Core/Tv/Series.cs b/src/NzbDrone.Core/Tv/Series.cs index 71618a718..cf398cab2 100644 --- a/src/NzbDrone.Core/Tv/Series.cs +++ b/src/NzbDrone.Core/Tv/Series.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using NzbDrone.Common.Extensions; using NzbDrone.Core.Datastore; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; namespace NzbDrone.Core.Tv @@ -30,7 +29,6 @@ public Series() public string AirTime { get; set; } public bool Monitored { get; set; } public int QualityProfileId { get; set; } - public int LanguageProfileId { get; set; } public bool SeasonFolder { get; set; } public DateTime? LastInfoSync { get; set; } public int Runtime { get; set; } @@ -49,7 +47,6 @@ public Series() public DateTime Added { get; set; } public DateTime? FirstAired { get; set; } public LazyLoaded QualityProfile { get; set; } - public LazyLoaded LanguageProfile { get; set; } public List Seasons { get; set; } public HashSet Tags { get; set; } @@ -67,7 +64,6 @@ public void ApplyChanges(Series otherSeries) Seasons = otherSeries.Seasons; Path = otherSeries.Path; QualityProfileId = otherSeries.QualityProfileId; - LanguageProfileId = otherSeries.LanguageProfileId; SeasonFolder = otherSeries.SeasonFolder; Monitored = otherSeries.Monitored; diff --git a/src/NzbDrone.Core/Validation/LanguageProfileExistsValidator.cs b/src/NzbDrone.Core/Validation/LanguageProfileExistsValidator.cs deleted file mode 100644 index 0e3b1e7db..000000000 --- a/src/NzbDrone.Core/Validation/LanguageProfileExistsValidator.cs +++ /dev/null @@ -1,26 +0,0 @@ -using FluentValidation.Validators; -using NzbDrone.Core.Profiles.Languages; - -namespace NzbDrone.Core.Validation -{ - public class LanguageProfileExistsValidator : PropertyValidator - { - private readonly ILanguageProfileService _profileService; - - public LanguageProfileExistsValidator(ILanguageProfileService profileService) - : base("Language profile does not exist") - { - _profileService = profileService; - } - - protected override bool IsValid(PropertyValidatorContext context) - { - if (context.PropertyValue == null) - { - return true; - } - - return _profileService.Exists((int)context.PropertyValue); - } - } -} diff --git a/src/NzbDrone.Integration.Test/ApiTests/EpisodeFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/EpisodeFixture.cs index 0c68be617..a3cb80f68 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/EpisodeFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/EpisodeFixture.cs @@ -23,7 +23,6 @@ private SeriesResource GivenSeriesWithEpisodes() var newSeries = Series.Lookup("archer").Single(c => c.TvdbId == 110381); newSeries.QualityProfileId = 1; - newSeries.LanguageProfileId = 1; newSeries.Path = @"C:\Test\Archer".AsOsAgnostic(); newSeries = Series.Post(newSeries); diff --git a/src/NzbDrone.Integration.Test/ApiTests/SeriesEditorFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/SeriesEditorFixture.cs index f2eeb3f07..7293ce97e 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/SeriesEditorFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/SeriesEditorFixture.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using FluentAssertions; using NUnit.Framework; using NzbDrone.Test.Common; @@ -18,7 +18,6 @@ private void GivenExistingSeries() var newSeries = Series.Lookup(title).First(); newSeries.QualityProfileId = 1; - newSeries.LanguageProfileId = 1; newSeries.Path = string.Format(@"C:\Test\{0}", title).AsOsAgnostic(); Series.Post(newSeries); diff --git a/src/NzbDrone.Integration.Test/ApiTests/SeriesFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/SeriesFixture.cs index 02c8acb24..4ec6b7607 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/SeriesFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/SeriesFixture.cs @@ -19,7 +19,6 @@ public void add_series_with_tags_should_store_them() var series = Series.Lookup("tvdb:266189").Single(); series.QualityProfileId = 1; - series.LanguageProfileId = 1; series.Path = Path.Combine(SeriesRootFolder, series.Title); series.Tags = new HashSet(); series.Tags.Add(tag.Id); @@ -65,7 +64,6 @@ public void add_series() var series = Series.Lookup("tvdb:266189").Single(); series.QualityProfileId = 1; - series.LanguageProfileId = 1; series.Path = Path.Combine(SeriesRootFolder, series.Title); var result = Series.Post(series); @@ -73,7 +71,6 @@ public void add_series() result.Should().NotBeNull(); result.Id.Should().NotBe(0); result.QualityProfileId.Should().Be(1); - result.LanguageProfileId.Should().Be(1); result.Path.Should().Be(Path.Combine(SeriesRootFolder, series.Title)); } diff --git a/src/NzbDrone.Integration.Test/IntegrationTestBase.cs b/src/NzbDrone.Integration.Test/IntegrationTestBase.cs index 1042b4156..92f51ebcb 100644 --- a/src/NzbDrone.Integration.Test/IntegrationTestBase.cs +++ b/src/NzbDrone.Integration.Test/IntegrationTestBase.cs @@ -247,7 +247,6 @@ public SeriesResource EnsureSeries(int tvdbId, string seriesTitle, bool? monitor var lookup = Series.Lookup("tvdb:" + tvdbId); var series = lookup.First(); series.QualityProfileId = 1; - series.LanguageProfileId = 1; series.Path = Path.Combine(SeriesRootFolder, series.Title); series.Monitored = true; series.Seasons.ForEach(v => v.Monitored = true); diff --git a/src/Sonarr.Api.V3/Blocklist/BlocklistResource.cs b/src/Sonarr.Api.V3/Blocklist/BlocklistResource.cs index 6bfb24669..7fa545550 100644 --- a/src/Sonarr.Api.V3/Blocklist/BlocklistResource.cs +++ b/src/Sonarr.Api.V3/Blocklist/BlocklistResource.cs @@ -15,7 +15,7 @@ public class BlocklistResource : RestResource public int SeriesId { get; set; } public List EpisodeIds { get; set; } public string SourceTitle { get; set; } - public Language Language { get; set; } + public List Languages { get; set; } public QualityModel Quality { get; set; } public List CustomFormats { get; set; } public DateTime Date { get; set; } @@ -42,7 +42,7 @@ public static BlocklistResource MapToResource(this NzbDrone.Core.Blocklisting.Bl SeriesId = model.SeriesId, EpisodeIds = model.EpisodeIds, SourceTitle = model.SourceTitle, - Language = model.Language, + Languages = model.Languages, Quality = model.Quality, CustomFormats = formatCalculator.ParseCustomFormat(model).ToResource(), Date = model.Date, diff --git a/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileController.cs b/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileController.cs index 92b4482ae..7df12bd21 100644 --- a/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileController.cs +++ b/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileController.cs @@ -130,9 +130,9 @@ public object SetQuality([FromBody] EpisodeFileListResource resource) foreach (var episodeFile in episodeFiles) { - if (resource.Language != null) + if (resource.Languages != null) { - episodeFile.Language = resource.Language; + episodeFile.Languages = resource.Languages; } if (resource.Quality != null) @@ -198,9 +198,9 @@ public object SetPropertiesBulk([FromBody] List resources) { var resourceEpisodeFile = resources.Single(r => r.Id == episodeFile.Id); - if (resourceEpisodeFile.Language != null) + if (resourceEpisodeFile.Languages != null) { - episodeFile.Language = resourceEpisodeFile.Language; + episodeFile.Languages = resourceEpisodeFile.Languages; } if (resourceEpisodeFile.Quality != null) diff --git a/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileListResource.cs b/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileListResource.cs index 07f063236..d285dd357 100644 --- a/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileListResource.cs +++ b/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileListResource.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using NzbDrone.Core.Languages; using NzbDrone.Core.Qualities; @@ -7,7 +7,7 @@ namespace Sonarr.Api.V3.EpisodeFiles public class EpisodeFileListResource { public List EpisodeFileIds { get; set; } - public Language Language { get; set; } + public List Languages { get; set; } public QualityModel Quality { get; set; } public string SceneName { get; set; } public string ReleaseGroup { get; set; } diff --git a/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileResource.cs b/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileResource.cs index 113fc4684..f45623382 100644 --- a/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileResource.cs +++ b/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileResource.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Languages; using NzbDrone.Core.MediaFiles; @@ -21,13 +20,12 @@ public class EpisodeFileResource : RestResource public DateTime DateAdded { get; set; } public string SceneName { get; set; } public string ReleaseGroup { get; set; } - public Language Language { get; set; } + public List Languages { get; set; } public QualityModel Quality { get; set; } public List CustomFormats { get; set; } public MediaInfoResource MediaInfo { get; set; } public bool QualityCutoffNotMet { get; set; } - public bool LanguageCutoffNotMet { get; set; } } public static class EpisodeFileResourceMapper @@ -52,7 +50,7 @@ private static EpisodeFileResource ToResource(this EpisodeFile model) DateAdded = model.DateAdded, SceneName = model.SceneName, ReleaseGroup = model.ReleaseGroup, - Language = model.Language, + Languages = model.Languages, Quality = model.Quality, MediaInfo = model.MediaInfo.ToResource(model.SceneName) @@ -79,11 +77,10 @@ public static EpisodeFileResource ToResource(this EpisodeFile model, NzbDrone.Co DateAdded = model.DateAdded, SceneName = model.SceneName, ReleaseGroup = model.ReleaseGroup, - Language = model.Language, + Languages = model.Languages, Quality = model.Quality, MediaInfo = model.MediaInfo.ToResource(model.SceneName), QualityCutoffNotMet = upgradableSpecification.QualityCutoffNotMet(series.QualityProfile.Value, model.Quality), - LanguageCutoffNotMet = upgradableSpecification.LanguageCutoffNotMet(series.LanguageProfile.Value, model.Language) }; } } diff --git a/src/Sonarr.Api.V3/History/HistoryController.cs b/src/Sonarr.Api.V3/History/HistoryController.cs index 66244ed47..58f0bc28c 100644 --- a/src/Sonarr.Api.V3/History/HistoryController.cs +++ b/src/Sonarr.Api.V3/History/HistoryController.cs @@ -50,7 +50,6 @@ protected HistoryResource MapToResource(EpisodeHistory model, bool includeSeries if (model.Series != null) { resource.QualityCutoffNotMet = _upgradableSpecification.QualityCutoffNotMet(model.Series.QualityProfile.Value, model.Quality); - resource.LanguageCutoffNotMet = _upgradableSpecification.LanguageCutoffNotMet(model.Series.LanguageProfile.Value, model.Language); } return resource; diff --git a/src/Sonarr.Api.V3/History/HistoryResource.cs b/src/Sonarr.Api.V3/History/HistoryResource.cs index aad162464..2afc6f26c 100644 --- a/src/Sonarr.Api.V3/History/HistoryResource.cs +++ b/src/Sonarr.Api.V3/History/HistoryResource.cs @@ -16,11 +16,10 @@ public class HistoryResource : RestResource public int EpisodeId { get; set; } public int SeriesId { get; set; } public string SourceTitle { get; set; } - public Language Language { get; set; } + public List Languages { get; set; } public QualityModel Quality { get; set; } public List CustomFormats { get; set; } public bool QualityCutoffNotMet { get; set; } - public bool LanguageCutoffNotMet { get; set; } public DateTime Date { get; set; } public string DownloadId { get; set; } @@ -48,7 +47,7 @@ public static HistoryResource ToResource(this EpisodeHistory model, ICustomForma EpisodeId = model.EpisodeId, SeriesId = model.SeriesId, SourceTitle = model.SourceTitle, - Language = model.Language, + Languages = model.Languages, Quality = model.Quality, CustomFormats = formatCalculator.ParseCustomFormat(model).ToResource(), diff --git a/src/Sonarr.Api.V3/ImportLists/ImportListController.cs b/src/Sonarr.Api.V3/ImportLists/ImportListController.cs index 21c4fccd1..3f564ec22 100644 --- a/src/Sonarr.Api.V3/ImportLists/ImportListController.cs +++ b/src/Sonarr.Api.V3/ImportLists/ImportListController.cs @@ -10,15 +10,13 @@ public class ImportListController : ProviderControllerBase s.QualityProfileId)); - Http.Validation.RuleBuilderExtensions.ValidId(SharedValidator.RuleFor(s => s.LanguageProfileId)); SharedValidator.RuleFor(c => c.RootFolderPath).IsValidPath(); SharedValidator.RuleFor(c => c.QualityProfileId).SetValidator(profileExistsValidator); - SharedValidator.RuleFor(c => c.LanguageProfileId).SetValidator(languageProfileExistsValidator); } } } diff --git a/src/Sonarr.Api.V3/ImportLists/ImportListResource.cs b/src/Sonarr.Api.V3/ImportLists/ImportListResource.cs index c98e5c316..040961caf 100644 --- a/src/Sonarr.Api.V3/ImportLists/ImportListResource.cs +++ b/src/Sonarr.Api.V3/ImportLists/ImportListResource.cs @@ -9,7 +9,6 @@ public class ImportListResource : ProviderResource public MonitorTypes ShouldMonitor { get; set; } public string RootFolderPath { get; set; } public int QualityProfileId { get; set; } - public int LanguageProfileId { get; set; } public SeriesTypes SeriesType { get; set; } public bool SeasonFolder { get; set; } public ImportListType ListType { get; set; } @@ -31,7 +30,6 @@ public override ImportListResource ToResource(ImportListDefinition definition) resource.ShouldMonitor = definition.ShouldMonitor; resource.RootFolderPath = definition.RootFolderPath; resource.QualityProfileId = definition.QualityProfileId; - resource.LanguageProfileId = definition.LanguageProfileId; resource.SeriesType = definition.SeriesType; resource.SeasonFolder = definition.SeasonFolder; resource.ListType = definition.ListType; @@ -53,7 +51,6 @@ public override ImportListDefinition ToModel(ImportListResource resource, Import definition.ShouldMonitor = resource.ShouldMonitor; definition.RootFolderPath = resource.RootFolderPath; definition.QualityProfileId = resource.QualityProfileId; - definition.LanguageProfileId = resource.LanguageProfileId; definition.SeriesType = resource.SeriesType; definition.SeasonFolder = resource.SeasonFolder; definition.ListType = resource.ListType; diff --git a/src/Sonarr.Api.V3/Indexers/ReleaseController.cs b/src/Sonarr.Api.V3/Indexers/ReleaseController.cs index 324493908..f0f73a121 100644 --- a/src/Sonarr.Api.V3/Indexers/ReleaseController.cs +++ b/src/Sonarr.Api.V3/Indexers/ReleaseController.cs @@ -12,7 +12,6 @@ using NzbDrone.Core.IndexerSearch; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Tv; using NzbDrone.Core.Validation; @@ -45,10 +44,9 @@ public ReleaseController(IFetchAndParseRss rssFetcherAndParser, IEpisodeService episodeService, IParsingService parsingService, ICacheManager cacheManager, - ILanguageProfileService languageProfileService, IQualityProfileService qualityProfileService, Logger logger) - : base(languageProfileService, qualityProfileService) + : base(qualityProfileService) { _rssFetcherAndParser = rssFetcherAndParser; _releaseSearchService = releaseSearchService; diff --git a/src/Sonarr.Api.V3/Indexers/ReleaseControllerBase.cs b/src/Sonarr.Api.V3/Indexers/ReleaseControllerBase.cs index 2c6e601a2..8522c3ec7 100644 --- a/src/Sonarr.Api.V3/Indexers/ReleaseControllerBase.cs +++ b/src/Sonarr.Api.V3/Indexers/ReleaseControllerBase.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using NzbDrone.Core.DecisionEngine; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using Sonarr.Http.REST; @@ -9,13 +8,10 @@ namespace Sonarr.Api.V3.Indexers { public abstract class ReleaseControllerBase : RestController { - private readonly LanguageProfile _languageProfile; private readonly QualityProfile _qualityProfile; - public ReleaseControllerBase(ILanguageProfileService languageProfileService, - IQualityProfileService qualityProfileService) + public ReleaseControllerBase(IQualityProfileService qualityProfileService) { - _languageProfile = languageProfileService.GetDefaultProfile(string.Empty); _qualityProfile = qualityProfileService.GetDefaultProfile(string.Empty); } @@ -45,7 +41,6 @@ protected virtual ReleaseResource MapDecision(DownloadDecision decision, int ini release.ReleaseWeight = initialWeight; release.QualityWeight = _qualityProfile.GetIndex(release.Quality.Quality).Index * 100; - release.LanguageWeight = _languageProfile.Languages.FindIndex(v => v.Language == release.Language) * 100; release.QualityWeight += release.Quality.Revision.Real * 10; release.QualityWeight += release.Quality.Revision.Version; diff --git a/src/Sonarr.Api.V3/Indexers/ReleasePushController.cs b/src/Sonarr.Api.V3/Indexers/ReleasePushController.cs index cfe629275..816ebf5b6 100644 --- a/src/Sonarr.Api.V3/Indexers/ReleasePushController.cs +++ b/src/Sonarr.Api.V3/Indexers/ReleasePushController.cs @@ -10,7 +10,6 @@ using NzbDrone.Core.Download; using NzbDrone.Core.Indexers; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using Sonarr.Http; @@ -27,10 +26,9 @@ public class ReleasePushController : ReleaseControllerBase public ReleasePushController(IMakeDownloadDecision downloadDecisionMaker, IProcessDownloadDecisions downloadDecisionProcessor, IIndexerFactory indexerFactory, - ILanguageProfileService languageProfileService, IQualityProfileService qualityProfileService, Logger logger) - : base(languageProfileService, qualityProfileService) + : base(qualityProfileService) { _downloadDecisionMaker = downloadDecisionMaker; _downloadDecisionProcessor = downloadDecisionProcessor; diff --git a/src/Sonarr.Api.V3/Indexers/ReleaseResource.cs b/src/Sonarr.Api.V3/Indexers/ReleaseResource.cs index cce7d8b7a..89642a89b 100644 --- a/src/Sonarr.Api.V3/Indexers/ReleaseResource.cs +++ b/src/Sonarr.Api.V3/Indexers/ReleaseResource.cs @@ -31,7 +31,7 @@ public class ReleaseResource : RestResource public bool FullSeason { get; set; } public bool SceneSource { get; set; } public int SeasonNumber { get; set; } - public Language Language { get; set; } + public List Languages { get; set; } public int LanguageWeight { get; set; } public string AirDate { get; set; } public string SeriesTitle { get; set; } @@ -104,7 +104,7 @@ public static ReleaseResource ToResource(this DownloadDecision model) Title = releaseInfo.Title, FullSeason = parsedEpisodeInfo.FullSeason, SeasonNumber = parsedEpisodeInfo.SeasonNumber, - Language = parsedEpisodeInfo.Language, + Languages = parsedEpisodeInfo.Languages, AirDate = parsedEpisodeInfo.AirDate, SeriesTitle = parsedEpisodeInfo.SeriesTitle, EpisodeNumbers = parsedEpisodeInfo.EpisodeNumbers, diff --git a/src/Sonarr.Api.V3/ManualImport/ManualImportController.cs b/src/Sonarr.Api.V3/ManualImport/ManualImportController.cs index bf14cb4b4..81d604821 100644 --- a/src/Sonarr.Api.V3/ManualImport/ManualImportController.cs +++ b/src/Sonarr.Api.V3/ManualImport/ManualImportController.cs @@ -38,16 +38,16 @@ public object ReprocessItems([FromBody] List item { foreach (var item in items) { - var processedItem = _manualImportService.ReprocessItem(item.Path, item.DownloadId, item.SeriesId, item.SeasonNumber, item.EpisodeIds ?? new List(), item.ReleaseGroup, item.Quality, item.Language); + var processedItem = _manualImportService.ReprocessItem(item.Path, item.DownloadId, item.SeriesId, item.SeasonNumber, item.EpisodeIds ?? new List(), item.ReleaseGroup, item.Quality, item.Languages); item.SeasonNumber = processedItem.SeasonNumber; item.Episodes = processedItem.Episodes.ToResource(); item.Rejections = processedItem.Rejections; // Only set the language/quality if they're unknown - if (item.Language == Language.Unknown) + if (item.Languages.Single() == Language.Unknown) { - item.Language = processedItem.Language; + item.Languages = processedItem.Languages; } if (item.Quality?.Quality == Quality.Unknown) diff --git a/src/Sonarr.Api.V3/ManualImport/ManualImportReprocessResource.cs b/src/Sonarr.Api.V3/ManualImport/ManualImportReprocessResource.cs index bab8f5589..c8817d328 100644 --- a/src/Sonarr.Api.V3/ManualImport/ManualImportReprocessResource.cs +++ b/src/Sonarr.Api.V3/ManualImport/ManualImportReprocessResource.cs @@ -15,7 +15,7 @@ public class ManualImportReprocessResource : RestResource public List Episodes { get; set; } public List EpisodeIds { get; set; } public QualityModel Quality { get; set; } - public Language Language { get; set; } + public List Languages { get; set; } public string ReleaseGroup { get; set; } public string DownloadId { get; set; } diff --git a/src/Sonarr.Api.V3/ManualImport/ManualImportResource.cs b/src/Sonarr.Api.V3/ManualImport/ManualImportResource.cs index 2c170430a..aed1e4dc1 100644 --- a/src/Sonarr.Api.V3/ManualImport/ManualImportResource.cs +++ b/src/Sonarr.Api.V3/ManualImport/ManualImportResource.cs @@ -24,7 +24,7 @@ public class ManualImportResource : RestResource public int? EpisodeFileId { get; set; } public string ReleaseGroup { get; set; } public QualityModel Quality { get; set; } - public Language Language { get; set; } + public List Languages { get; set; } public int QualityWeight { get; set; } public string DownloadId { get; set; } public IEnumerable Rejections { get; set; } @@ -53,7 +53,7 @@ public static ManualImportResource ToResource(this ManualImportItem model) EpisodeFileId = model.EpisodeFileId, ReleaseGroup = model.ReleaseGroup, Quality = model.Quality, - Language = model.Language, + Languages = model.Languages, //QualityWeight DownloadId = model.DownloadId, diff --git a/src/Sonarr.Api.V3/Profiles/Language/LanguageProfileController.cs b/src/Sonarr.Api.V3/Profiles/Language/LanguageProfileController.cs deleted file mode 100644 index 2bdb91478..000000000 --- a/src/Sonarr.Api.V3/Profiles/Language/LanguageProfileController.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using FluentValidation; -using Microsoft.AspNetCore.Mvc; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.Profiles.Languages; -using Sonarr.Http; -using Sonarr.Http.REST; -using Sonarr.Http.REST.Attributes; - -namespace Sonarr.Api.V3.Profiles.Language -{ - [V3ApiController] - public class LanguageProfileController : RestController - { - private readonly ILanguageProfileService _profileService; - - public LanguageProfileController(ILanguageProfileService profileService) - { - _profileService = profileService; - SharedValidator.RuleFor(c => c.Name).NotEmpty(); - SharedValidator.RuleFor(c => c.Cutoff).NotNull(); - SharedValidator.RuleFor(c => c.Languages).MustHaveAllowedLanguage(); - } - - [RestPostById] - [Consumes("application/json")] - public ActionResult Create(LanguageProfileResource resource) - { - var model = resource.ToModel(); - model = _profileService.Add(model); - return Created(model.Id); - } - - [RestDeleteById] - public void DeleteProfile(int id) - { - _profileService.Delete(id); - } - - [RestPutById] - [Consumes("application/json")] - public ActionResult Update(LanguageProfileResource resource) - { - var model = resource.ToModel(); - - _profileService.Update(model); - - return Accepted(model.Id); - } - - protected override LanguageProfileResource GetResourceById(int id) - { - return _profileService.Get(id).ToResource(); - } - - [HttpGet] - [Produces("application/json")] - public List GetAll() - { - return _profileService.All().ToResource(); - } - } -} diff --git a/src/Sonarr.Api.V3/Profiles/Language/LanguageProfileResource.cs b/src/Sonarr.Api.V3/Profiles/Language/LanguageProfileResource.cs deleted file mode 100644 index a32882eaf..000000000 --- a/src/Sonarr.Api.V3/Profiles/Language/LanguageProfileResource.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using NzbDrone.Core.Profiles.Languages; -using Sonarr.Http.REST; - -namespace Sonarr.Api.V3.Profiles.Language -{ - public class LanguageProfileResource : RestResource - { - public string Name { get; set; } - public bool UpgradeAllowed { get; set; } - public NzbDrone.Core.Languages.Language Cutoff { get; set; } - public List Languages { get; set; } - } - - public class LanguageProfileItemResource : RestResource - { - public NzbDrone.Core.Languages.Language Language { get; set; } - public bool Allowed { get; set; } - } - - public static class LanguageProfileResourceMapper - { - public static LanguageProfileResource ToResource(this LanguageProfile model) - { - if (model == null) - { - return null; - } - - return new LanguageProfileResource - { - Id = model.Id, - Name = model.Name, - UpgradeAllowed = model.UpgradeAllowed, - Cutoff = model.Cutoff, - Languages = model.Languages.ConvertAll(ToResource) - }; - } - - public static LanguageProfileItemResource ToResource(this LanguageProfileItem model) - { - if (model == null) - { - return null; - } - - return new LanguageProfileItemResource - { - Language = model.Language, - Allowed = model.Allowed - }; - } - - public static LanguageProfile ToModel(this LanguageProfileResource resource) - { - if (resource == null) - { - return null; - } - - return new LanguageProfile - { - Id = resource.Id, - Name = resource.Name, - UpgradeAllowed = resource.UpgradeAllowed, - Cutoff = (NzbDrone.Core.Languages.Language)resource.Cutoff.Id, - Languages = resource.Languages.ConvertAll(ToModel) - }; - } - - public static LanguageProfileItem ToModel(this LanguageProfileItemResource resource) - { - if (resource == null) - { - return null; - } - - return new LanguageProfileItem - { - Language = (NzbDrone.Core.Languages.Language)resource.Language.Id, - Allowed = resource.Allowed - }; - } - - public static List ToResource(this IEnumerable models) - { - return models.Select(ToResource).ToList(); - } - } -} diff --git a/src/Sonarr.Api.V3/Profiles/Language/LanguageProfileSchemaController.cs b/src/Sonarr.Api.V3/Profiles/Language/LanguageProfileSchemaController.cs deleted file mode 100644 index e0631df72..000000000 --- a/src/Sonarr.Api.V3/Profiles/Language/LanguageProfileSchemaController.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using NzbDrone.Core.Profiles.Languages; -using Sonarr.Http; - -namespace Sonarr.Api.V3.Profiles.Language -{ - [V3ApiController("languageprofile/schema")] - public class LanguageProfileSchemaController : Controller - { - private readonly ILanguageProfileService _profileService; - - public LanguageProfileSchemaController(ILanguageProfileService profileService) - { - _profileService = profileService; - } - - [HttpGet] - [Produces("application/json")] - public LanguageProfileResource GetSchema() - { - var qualityProfile = _profileService.GetDefaultProfile(string.Empty); - - return qualityProfile.ToResource(); - } - } -} diff --git a/src/Sonarr.Api.V3/Profiles/Language/LanguageValidator.cs b/src/Sonarr.Api.V3/Profiles/Language/LanguageValidator.cs deleted file mode 100644 index a74724057..000000000 --- a/src/Sonarr.Api.V3/Profiles/Language/LanguageValidator.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using FluentValidation; -using FluentValidation.Validators; - -namespace Sonarr.Api.V3.Profiles.Language -{ - public static class LanguageValidation - { - public static IRuleBuilderOptions> MustHaveAllowedLanguage(this IRuleBuilder> ruleBuilder) - { - ruleBuilder.SetValidator(new NotEmptyValidator(null)); - - return ruleBuilder.SetValidator(new LanguageValidator()); - } - } - - public class LanguageValidator : PropertyValidator - { - public LanguageValidator() - : base("Must have at least one allowed language") - { - } - - protected override bool IsValid(PropertyValidatorContext context) - { - var list = context.PropertyValue as IList; - - if (list == null) - { - return false; - } - - if (!list.Any(c => c.Allowed)) - { - return false; - } - - return true; - } - } -} diff --git a/src/Sonarr.Api.V3/Profiles/Languages/LanguageController.cs b/src/Sonarr.Api.V3/Profiles/Languages/LanguageController.cs new file mode 100644 index 000000000..69945f6dc --- /dev/null +++ b/src/Sonarr.Api.V3/Profiles/Languages/LanguageController.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using NzbDrone.Core.Languages; +using Sonarr.Http; +using Sonarr.Http.REST; + +namespace Sonarr.Api.V3.Profiles.Languages +{ + [V3ApiController] + public class LanguageController : RestController + { + protected override LanguageResource GetResourceById(int id) + { + var language = (Language)id; + + return new LanguageResource + { + Id = (int)language, + Name = language.ToString() + }; + } + + [HttpGet] + public List GetAll() + { + var languageResources = Language.All.Select(l => new LanguageResource + { + Id = (int)l, + Name = l.ToString() + }) + .OrderBy(l => l.Id > 0).ThenBy(l => l.Name) + .ToList(); + + return languageResources; + } + } +} diff --git a/src/Sonarr.Api.V3/Profiles/Languages/LanguageResource.cs b/src/Sonarr.Api.V3/Profiles/Languages/LanguageResource.cs new file mode 100644 index 000000000..873a89fd2 --- /dev/null +++ b/src/Sonarr.Api.V3/Profiles/Languages/LanguageResource.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; +using Sonarr.Http.REST; + +namespace Sonarr.Api.V3.Profiles.Languages +{ + public class LanguageResource : RestResource + { + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public new int Id { get; set; } + public string Name { get; set; } + public string NameLower => Name.ToLowerInvariant(); + } +} diff --git a/src/Sonarr.Api.V3/Queue/QueueController.cs b/src/Sonarr.Api.V3/Queue/QueueController.cs index 643f202c3..cbf9d96eb 100644 --- a/src/Sonarr.Api.V3/Queue/QueueController.cs +++ b/src/Sonarr.Api.V3/Queue/QueueController.cs @@ -11,7 +11,6 @@ using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.Languages; using NzbDrone.Core.Messaging.Events; -using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Queue; @@ -30,7 +29,6 @@ public class QueueController : RestControllerWithSignalR GetQueue(bool includeUnknownSeriesItems = f ? fullQueue.OrderBy(q => q.Quality, _qualityComparer) : fullQueue.OrderByDescending(q => q.Quality, _qualityComparer); } - else if (pagingSpec.SortKey == "language") + else if (pagingSpec.SortKey == "languages") { ordered = ascending - ? fullQueue.OrderBy(q => q.Language, _languageComparer) - : fullQueue.OrderByDescending(q => q.Language, _languageComparer); + ? fullQueue.OrderBy(q => q.Languages, new LanguagesComparer()) + : fullQueue.OrderByDescending(q => q.Languages, new LanguagesComparer()); } else { @@ -197,8 +193,8 @@ public PagingResource GetQueue(bool includeUnknownSeriesItems = f return q => q.Episode?.AirDateUtc ?? DateTime.MinValue; case "episodes.title": return q => q.Episode?.Title ?? string.Empty; - case "language": - return q => q.Language; + case "languages": + return q => q.Languages; case "quality": return q => q.Quality; case "progress": diff --git a/src/Sonarr.Api.V3/Queue/QueueResource.cs b/src/Sonarr.Api.V3/Queue/QueueResource.cs index 6cfe30662..c9cef079f 100644 --- a/src/Sonarr.Api.V3/Queue/QueueResource.cs +++ b/src/Sonarr.Api.V3/Queue/QueueResource.cs @@ -19,7 +19,7 @@ public class QueueResource : RestResource public int? EpisodeId { get; set; } public SeriesResource Series { get; set; } public EpisodeResource Episode { get; set; } - public Language Language { get; set; } + public List Languages { get; set; } public QualityModel Quality { get; set; } public List CustomFormats { get; set; } public decimal Size { get; set; } @@ -55,7 +55,7 @@ public static QueueResource ToResource(this NzbDrone.Core.Queue.Queue model, boo EpisodeId = model.Episode?.Id, Series = includeSeries && model.Series != null ? model.Series.ToResource() : null, Episode = includeEpisode && model.Episode != null ? model.Episode.ToResource() : null, - Language = model.Language, + Languages = model.Languages, Quality = model.Quality, CustomFormats = model.RemoteEpisode?.CustomFormats?.ToResource(), Size = model.Size, diff --git a/src/Sonarr.Api.V3/Series/SeriesController.cs b/src/Sonarr.Api.V3/Series/SeriesController.cs index efd8903c5..9155f6c8b 100644 --- a/src/Sonarr.Api.V3/Series/SeriesController.cs +++ b/src/Sonarr.Api.V3/Series/SeriesController.cs @@ -58,7 +58,6 @@ public SeriesController(IBroadcastSignalRMessage signalRBroadcaster, SeriesAncestorValidator seriesAncestorValidator, SystemFolderValidator systemFolderValidator, ProfileExistsValidator profileExistsValidator, - LanguageProfileExistsValidator languageProfileExistsValidator, SeriesFolderAsRootFolderValidator seriesFolderAsRootFolderValidator) : base(signalRBroadcaster) { @@ -84,7 +83,6 @@ public SeriesController(IBroadcastSignalRMessage signalRBroadcaster, .When(s => !s.Path.IsNullOrWhiteSpace()); SharedValidator.RuleFor(s => s.QualityProfileId).SetValidator(profileExistsValidator); - SharedValidator.RuleFor(s => s.LanguageProfileId).SetValidator(languageProfileExistsValidator); PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace()); PostValidator.RuleFor(s => s.RootFolderPath) diff --git a/src/Sonarr.Api.V3/Series/SeriesEditorController.cs b/src/Sonarr.Api.V3/Series/SeriesEditorController.cs index fbb8fd350..1af3f0b4c 100644 --- a/src/Sonarr.Api.V3/Series/SeriesEditorController.cs +++ b/src/Sonarr.Api.V3/Series/SeriesEditorController.cs @@ -39,11 +39,6 @@ public object SaveAll([FromBody] SeriesEditorResource resource) series.QualityProfileId = resource.QualityProfileId.Value; } - if (resource.LanguageProfileId.HasValue) - { - series.LanguageProfileId = resource.LanguageProfileId.Value; - } - if (resource.SeriesType.HasValue) { series.SeriesType = resource.SeriesType.Value; diff --git a/src/Sonarr.Api.V3/Series/SeriesEditorResource.cs b/src/Sonarr.Api.V3/Series/SeriesEditorResource.cs index 273057ab6..8317666f9 100644 --- a/src/Sonarr.Api.V3/Series/SeriesEditorResource.cs +++ b/src/Sonarr.Api.V3/Series/SeriesEditorResource.cs @@ -8,7 +8,6 @@ public class SeriesEditorResource public List SeriesIds { get; set; } public bool? Monitored { get; set; } public int? QualityProfileId { get; set; } - public int? LanguageProfileId { get; set; } public SeriesTypes? SeriesType { get; set; } public bool? SeasonFolder { get; set; } public string RootFolderPath { get; set; } diff --git a/src/Sonarr.Api.V3/Series/SeriesResource.cs b/src/Sonarr.Api.V3/Series/SeriesResource.cs index 918a7f276..f7efc9f37 100644 --- a/src/Sonarr.Api.V3/Series/SeriesResource.cs +++ b/src/Sonarr.Api.V3/Series/SeriesResource.cs @@ -38,7 +38,6 @@ public class SeriesResource : RestResource //View & Edit public string Path { get; set; } public int QualityProfileId { get; set; } - public int LanguageProfileId { get; set; } //Editing Only public bool SeasonFolder { get; set; } @@ -104,7 +103,6 @@ public static SeriesResource ToResource(this NzbDrone.Core.Tv.Series model, bool Path = model.Path, QualityProfileId = model.QualityProfileId, - LanguageProfileId = model.LanguageProfileId, SeasonFolder = model.SeasonFolder, Monitored = model.Monitored, @@ -166,7 +164,6 @@ public static NzbDrone.Core.Tv.Series ToModel(this SeriesResource resource) Path = resource.Path, QualityProfileId = resource.QualityProfileId, - LanguageProfileId = resource.LanguageProfileId, SeasonFolder = resource.SeasonFolder, Monitored = resource.Monitored, diff --git a/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs b/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs index a82b5a557..5fd057af9 100644 --- a/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs +++ b/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs @@ -169,33 +169,44 @@ private static Tuple[] GetProperties(Typ private static List GetSelectOptions(Type selectOptions) { - var options = selectOptions.GetFields().Where(v => v.IsStatic).Select(v => + if (selectOptions.IsEnum) { - var name = v.Name.Replace('_', ' '); - var value = Convert.ToInt32(v.GetRawConstantValue()); - var attrib = v.GetCustomAttribute(); - if (attrib != null) + var options = selectOptions.GetFields().Where(v => v.IsStatic).Select(v => { - return new SelectOption + var name = v.Name.Replace('_', ' '); + var value = Convert.ToInt32(v.GetRawConstantValue()); + var attrib = v.GetCustomAttribute(); + if (attrib != null) { - Value = value, - Name = attrib.Label ?? name, - Order = attrib.Order, - Hint = attrib.Hint ?? $"({value})" - }; - } - else - { - return new SelectOption + return new SelectOption + { + Value = value, + Name = attrib.Label ?? name, + Order = attrib.Order, + Hint = attrib.Hint ?? $"({value})" + }; + } + else { - Value = value, - Name = name, - Order = value - }; - } - }); + return new SelectOption + { + Value = value, + Name = name, + Order = value + }; + } + }); - return options.OrderBy(o => o.Order).ToList(); + return options.OrderBy(o => o.Order).ToList(); + } + + if (typeof(ISelectOptionsConverter).IsAssignableFrom(selectOptions)) + { + var converter = Activator.CreateInstance(selectOptions) as ISelectOptionsConverter; + return converter.GetSelectOptions(); + } + + throw new NotSupportedException(); } private static Func GetValueConverter(Type propertyType)