1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-14 15:46:43 -04:00

Compare commits

...

45 Commits

Author SHA1 Message Date
Qstick
793cf22c3b WIP: New: Exclusion Lists 2020-10-17 02:16:17 -04:00
Qstick
76cabb4927 Use new multi-select component for indexer languages 2020-10-17 01:38:41 -04:00
nitsua
5b83d09d5e Remove some instances of moment that are not needed on the index to reduce the load 2020-10-17 01:04:50 -04:00
Daniel Martin Gonzalez
82eadcffaa New: Add Option to localize metadata written in .nfo if available (#5060)
* Add Option to localize metadata written in .nfo if available

* Fix Pull Request comments
2020-10-17 01:03:46 -04:00
Qstick
1ebb71db59 Fixed: Import Extra files from Subfolders
Fixes: #4646
2020-10-17 00:17:38 -04:00
Qstick
662b3894c2 Fixed: Make Notification trigger texts consistent
Fixes #5012
2020-10-17 00:06:23 -04:00
Qstick
a1c21af9b5 Fixed: (Windows) clean up extraneous files in build folder during installation
Fixes #5200
Fixes #5203

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2020-10-16 23:33:03 -04:00
Qstick
67fca87c44 Added PrivacyLevel option to FieldDefinition for later usage
Fixes #5107

Co-Authored-By: Taloth <Taloth@users.noreply.github.com>
2020-10-16 23:27:01 -04:00
Qstick
6ee2780370 Fixed some mediainfo subtitle codes
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2020-10-16 23:06:17 -04:00
Qstick
0086e2699e Protect against undefined SizeOnDisk
Fixes #5205

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2020-10-16 22:59:21 -04:00
Qstick
37c9701237 Fixed: Rerender Overviews on Overview option changes
Fixes #5150

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2020-10-16 22:55:28 -04:00
Qstick
6d452d8479 New: Bulk remove from Blacklist
Fixes #5194

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2020-10-16 22:45:23 -04:00
Qstick
e82f0c01d8 Re-saving edited providers will forcibly save them
Fixes #5192

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2020-10-16 22:28:19 -04:00
Qstick
ee456c3291 Fixed: MapCoversToLocal for tmdbid queries to all movies endpoint 2020-10-16 22:19:17 -04:00
kingii98
9986f0119b Translated using Weblate (Russian) [skip ci]
Currently translated at 6.7% (57 of 844 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/radarr/radarr/ru/
2020-10-16 21:48:58 +00:00
Csaba
5d4da26195 Translated using Weblate (Hungarian) [skip ci]
Currently translated at 100.0% (844 of 844 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/radarr/radarr/hu/
2020-10-16 21:48:58 +00:00
memnos
5ed448c930 Translated using Weblate (Italian) [skip ci]
Currently translated at 100.0% (844 of 844 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/radarr/radarr/it/
2020-10-16 21:48:58 +00:00
jpalenz77
9263e31b7b Translated using Weblate (Spanish) [skip ci]
Currently translated at 100.0% (844 of 844 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/radarr/radarr/es/
2020-10-16 21:48:57 +00:00
angrycuban13
516122c6f3 Translated using Weblate (Spanish) [skip ci]
Currently translated at 100.0% (843 of 843 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/radarr/radarr/es/
2020-10-16 03:00:36 +00:00
foXaCe
256a50abab Translated using Weblate (French) [skip ci]
Currently translated at 100.0% (843 of 843 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/radarr/radarr/fr/
2020-10-16 03:00:36 +00:00
EnorMOZ
315929bc5e Update "TagsHelpText" with similar language as sonarr (#5218)
* Update "TagsHelpText" with similar language as sonarr

* Use new translation key
2020-10-15 23:00:31 -04:00
Qstick
4a681601b2 Skip Screenshot on 2nd build attempt 2020-10-15 21:14:27 -04:00
Csaba
ab3b5bdf8b Translated using Weblate (Hungarian) [skip ci]
Currently translated at 100.0% (842 of 842 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/radarr/radarr/hu/
2020-10-15 14:38:36 +00:00
Qstick
e52288bd67 New: Edit RlsGroup, Flags, and Edition for Movie Files (#5183)
* New: Edit RlsGroup and Edition for Movie Files

* fixup! remove console log

* fixup! translation
2020-10-15 10:38:30 -04:00
servarr[bot]
f2f26d88b9 New: Differentiate between short term and long term (more than 6 hours) indexer failures (#5202)
* New: Differentiate between short term and long term (more than 6 hours) indexer failures

(cherry picked from commit 2adedb97da5ad31b65f0dc2eec5c263efe95731f)

* fixup! Mock Localization

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
Co-authored-by: Qstick <qstick@gmail.com>
2020-10-15 07:20:02 -04:00
nitsua
27354507cb Fix issue with IMDb lists (some not all) not parsing properly causing an issue when trying to add them 2020-10-14 15:08:08 -04:00
Qstick
b7aa1df219 Fix Automation/cleanup build yml (#5211)
* Cleanup build yml

* fixup! bump chrome driver
2020-10-13 21:53:17 -04:00
ta264
2d7942d69c Fixed: Speed up RSS sync 2020-10-13 20:54:33 -04:00
ta264
0a8dd85856 Fixed: Speed up initial movie load when opening the UI 2020-10-13 20:54:33 -04:00
ta264
f917d0e9bc Add FileInfo utility functions to DiskProvider 2020-10-13 20:54:33 -04:00
kingii98
024e4df99c Translated using Weblate (Russian) [skip ci]
Currently translated at 6.1% (52 of 840 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/radarr/radarr/ru/
2020-10-13 16:48:58 +00:00
Csaba
49fa402c55 Translated using Weblate (Hungarian) [skip ci]
Currently translated at 100.0% (840 of 840 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/radarr/radarr/hu/
2020-10-13 16:48:57 +00:00
Qstick
02c95658c4 Windows installer improvements
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2020-10-11 22:43:08 -04:00
Mark McDowall
3bc4231640 New: Health events for Webhooks
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2020-10-11 22:40:18 -04:00
Mark McDowall
a9a0d47f9f Fixed: Copying passwords
(cherry picked from commit c871b3f9487b9bfeb3726d763a632a772b420a0a)
2020-10-11 20:36:46 -04:00
Florian
0dd05b2dac Translated using Weblate (French) [skip ci]
Currently translated at 100.0% (840 of 840 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/radarr/radarr/fr/
2020-10-11 21:48:51 +00:00
Qstick
3986433884 Fixed: Default a Movie object with Empty Ratings 2020-10-11 01:26:53 -04:00
Mark McDowall
f637976530 Fixed: Parsing of URLs with double slashes in the path
(cherry picked from commit 0c7743e786749b333333d282412ff76fc10aba65)
2020-10-10 17:16:21 -04:00
Qstick
603d26bb5f Take Screenshot on Automation tests for build status notifications 2020-10-09 21:51:59 -04:00
Will Segatto
6b41ad7442 Translated using Weblate (Portuguese) [skip ci]
Currently translated at 100.0% (840 of 840 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/radarr/radarr/pt/
2020-10-10 00:49:05 +00:00
hotio
4c049ac3d9 Translated using Weblate (Dutch) [skip ci]
Currently translated at 97.9% (823 of 840 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/radarr/radarr/nl/
2020-10-10 00:49:04 +00:00
Csaba
84df3e8b5b Translated using Weblate (Hungarian) [skip ci]
Currently translated at 91.4% (768 of 840 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/radarr/radarr/hu/
2020-10-10 00:49:03 +00:00
jpalenz77
0ff76e14bb Translated using Weblate (Spanish) [skip ci]
Currently translated at 100.0% (840 of 840 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/radarr/radarr/es/
2020-10-10 00:48:55 +00:00
Florian
5433c6364c Translated using Weblate (French) [skip ci]
Currently translated at 100.0% (840 of 840 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/radarr/radarr/fr/
2020-10-10 00:48:50 +00:00
geogolem
038e3d5c44 update trayIcon with new Radarr Icon 2020-10-08 14:27:07 -04:00
156 changed files with 2460 additions and 428 deletions

View File

@@ -386,11 +386,6 @@ stages:
- powershell: Set-Service SCardSvr -StartupType Manual
displayName: Enable Windows Test Service
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- bash: |
wget https://github.com/acoustid/chromaprint/releases/download/v1.4.3/chromaprint-fpcalc-1.4.3-linux-x86_64.tar.gz
sudo tar xf chromaprint-fpcalc-1.4.3-linux-x86_64.tar.gz --strip-components=1 --directory /usr/bin
displayName: Install fpcalc
condition: and(succeeded(), eq(variables['osName'], 'Linux'))
- bash: |
SYMLINK=6_6_0
MONOPREFIX=/Library/Frameworks/Mono.framework/Versions/$SYMLINK
@@ -693,24 +688,21 @@ stages:
mkdir -p ./bin/
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Radarr/. ./bin/
displayName: Move Package Contents
- bash: |
if [[ $OSNAME == "Mac" ]]; then
url=https://github.com/mozilla/geckodriver/releases/download/v0.27.0/geckodriver-v0.27.0-macos.tar.gz
elif [[ $OSNAME == "Linux" ]]; then
url=https://github.com/mozilla/geckodriver/releases/download/v0.27.0/geckodriver-v0.27.0-linux64.tar.gz
else
echo "Unhandled OS"
exit 1
fi
curl -s -L "$url" | tar -xz
chmod +x geckodriver
mv geckodriver _tests
displayName: Install Gecko Driver
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
- bash: |
chmod a+x ${TESTSFOLDER}/test.sh
${TESTSFOLDER}/test.sh ${OSNAME} Automation Test
displayName: Run Automation Tests
- task: CopyFiles@2
displayName: 'Copy Screenshot to: $(Build.ArtifactStagingDirectory)'
inputs:
SourceFolder: '$(Build.SourcesDirectory)'
Contents: |
**/*_test_screenshot.png
TargetFolder: '$(Build.ArtifactStagingDirectory)/screenshots'
- publish: $(Build.ArtifactStagingDirectory)/screenshots
artifact: '$(osName)AutomationScreenshots'
displayName: Publish Screenshot Bundle
condition: and(succeeded(), eq(variables['System.JobAttempt'], '1'))
- task: PublishTestResults@2
inputs:
testResultsFormat: 'NUnit'
@@ -859,8 +851,15 @@ stages:
- job:
displayName: Discord Notification
pool:
vmImage: 'ubuntu-18.04'
vmImage: 'windows-2019'
steps:
- task: DownloadPipelineArtifact@2
continueOnError: true
displayName: Download Screenshot Artifact
inputs:
buildType: 'current'
artifactName: 'WindowsAutomationScreenshots'
targetPath: $(Build.SourcesDirectory)
- checkout: none
- powershell: |
iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/Servarr/AzureDiscordNotify/master/DiscordNotify.ps1'))

View File

@@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
@@ -10,12 +11,84 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import TablePager from 'Components/Table/TablePager';
import { align, icons } from 'Helpers/Props';
import { align, icons, kinds } from 'Helpers/Props';
import getRemovedItems from 'Utilities/Object/getRemovedItems';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
import BlacklistRowConnector from './BlacklistRowConnector';
class Blacklist extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
allSelected: false,
allUnselected: false,
lastToggled: null,
selectedState: {},
isConfirmRemoveModalOpen: false,
items: props.items
};
}
componentDidUpdate(prevProps) {
const {
items
} = this.props;
if (hasDifferentItems(prevProps.items, items)) {
this.setState((state) => {
return {
...removeOldSelectedState(state, getRemovedItems(prevProps.items, items)),
items
};
});
return;
}
}
//
// Control
getSelectedIds = () => {
return getSelectedIds(this.state.selectedState);
}
//
// Listeners
onSelectAllChange = ({ value }) => {
this.setState(selectAll(this.state.selectedState, value));
}
onSelectedChange = ({ id, value, shiftKey = false }) => {
this.setState((state) => {
return toggleSelected(state, this.props.items, id, value, shiftKey);
});
}
onRemoveSelectedPress = () => {
this.setState({ isConfirmRemoveModalOpen: true });
}
onRemoveSelectedConfirmed = () => {
this.props.onRemoveSelected(this.getSelectedIds());
this.setState({ isConfirmRemoveModalOpen: false });
}
onConfirmRemoveModalClose = () => {
this.setState({ isConfirmRemoveModalOpen: false });
}
//
// Render
@@ -27,15 +100,33 @@ class Blacklist extends Component {
items,
columns,
totalRecords,
isRemoving,
isClearingBlacklistExecuting,
onClearBlacklistPress,
...otherProps
} = this.props;
const {
allSelected,
allUnselected,
selectedState,
isConfirmRemoveModalOpen
} = this.state;
const selectedIds = this.getSelectedIds();
return (
<PageContent title={translate('Blacklist')}>
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
label="Remove Selected"
iconName={icons.REMOVE}
isDisabled={!selectedIds.length}
isSpinning={isRemoving}
onPress={this.onRemoveSelectedPress}
/>
<PageToolbarButton
label={translate('Clear')}
iconName={icons.CLEAR}
@@ -81,8 +172,12 @@ class Blacklist extends Component {
isPopulated && !error && !!items.length &&
<div>
<Table
selectAll={true}
allSelected={allSelected}
allUnselected={allUnselected}
columns={columns}
{...otherProps}
onSelectAllChange={this.onSelectAllChange}
>
<TableBody>
{
@@ -90,8 +185,10 @@ class Blacklist extends Component {
return (
<BlacklistRowConnector
key={item.id}
isSelected={selectedState[item.id] || false}
columns={columns}
{...item}
onSelectedChange={this.onSelectedChange}
/>
);
})
@@ -107,6 +204,16 @@ class Blacklist extends Component {
</div>
}
</PageContentBody>
<ConfirmModal
isOpen={isConfirmRemoveModalOpen}
kind={kinds.DANGER}
title="Remove Selected"
message={'Are you sure you want to remove the selected items from the blacklist?'}
confirmLabel="Remove Selected"
onConfirm={this.onRemoveSelectedConfirmed}
onCancel={this.onConfirmRemoveModalClose}
/>
</PageContent>
);
}
@@ -119,7 +226,9 @@ Blacklist.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
totalRecords: PropTypes.number,
isRemoving: PropTypes.bool.isRequired,
isClearingBlacklistExecuting: PropTypes.bool.isRequired,
onRemoveSelected: PropTypes.func.isRequired,
onClearBlacklistPress: PropTypes.func.isRequired
};

View File

@@ -89,6 +89,10 @@ class BlacklistConnector extends Component {
this.props.gotoBlacklistPage({ page });
}
onRemoveSelected = (ids) => {
this.props.removeBlacklistItems({ ids });
}
onSortPress = (sortKey) => {
this.props.setBlacklistSort({ sortKey });
}
@@ -124,6 +128,7 @@ class BlacklistConnector extends Component {
onNextPagePress={this.onNextPagePress}
onLastPagePress={this.onLastPagePress}
onPageSelect={this.onPageSelect}
onRemoveSelected={this.onRemoveSelected}
onSortPress={this.onSortPress}
onTableOptionChange={this.onTableOptionChange}
onClearBlacklistPress={this.onClearBlacklistPress}
@@ -143,6 +148,7 @@ BlacklistConnector.propTypes = {
gotoBlacklistNextPage: PropTypes.func.isRequired,
gotoBlacklistLastPage: PropTypes.func.isRequired,
gotoBlacklistPage: PropTypes.func.isRequired,
removeBlacklistItems: PropTypes.func.isRequired,
setBlacklistSort: PropTypes.func.isRequired,
setBlacklistTableOption: PropTypes.func.isRequired,
clearBlacklist: PropTypes.func.isRequired,

View File

@@ -3,6 +3,7 @@ import React, { Component } from 'react';
import IconButton from 'Components/Link/IconButton';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import TableRow from 'Components/Table/TableRow';
import { icons, kinds } from 'Helpers/Props';
import MovieFormats from 'Movie/MovieFormats';
@@ -42,6 +43,7 @@ class BlacklistRow extends Component {
render() {
const {
id,
movie,
sourceTitle,
quality,
@@ -51,7 +53,9 @@ class BlacklistRow extends Component {
protocol,
indexer,
message,
isSelected,
columns,
onSelectedChange,
onRemovePress
} = this.props;
@@ -61,6 +65,12 @@ class BlacklistRow extends Component {
return (
<TableRow>
<TableSelectCell
id={id}
isSelected={isSelected}
onSelectedChange={onSelectedChange}
/>
{
columns.map((column) => {
const {
@@ -194,7 +204,9 @@ BlacklistRow.propTypes = {
protocol: PropTypes.string.isRequired,
indexer: PropTypes.string,
message: PropTypes.string,
isSelected: PropTypes.bool.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onSelectedChange: PropTypes.func.isRequired,
onRemovePress: PropTypes.func.isRequired
};

View File

@@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { removeFromBlacklist } from 'Store/Actions/blacklistActions';
import { removeBlacklistItem } from 'Store/Actions/blacklistActions';
import createMovieSelector from 'Store/Selectors/createMovieSelector';
import BlacklistRow from './BlacklistRow';
@@ -18,7 +18,7 @@ function createMapStateToProps() {
function createMapDispatchToProps(dispatch, props) {
return {
onRemovePress() {
dispatch(removeFromBlacklist({ id: props.id }));
dispatch(removeBlacklistItem({ id: props.id }));
}
};
}

View File

@@ -10,6 +10,7 @@ import CheckInput from './CheckInput';
import DeviceInputConnector from './DeviceInputConnector';
import EnhancedSelectInput from './EnhancedSelectInput';
import FormInputHelpText from './FormInputHelpText';
import IndexerFlagsSelectInputConnector from './IndexerFlagsSelectInputConnector';
import KeyValueListInput from './KeyValueListInput';
import MovieMonitoredSelectInput from './MovieMonitoredSelectInput';
import NumberInput from './NumberInput';
@@ -66,6 +67,9 @@ function getComponent(type) {
case inputTypes.ROOT_FOLDER_SELECT:
return RootFolderSelectInputConnector;
case inputTypes.INDEXER_FLAGS_SELECT:
return IndexerFlagsSelectInputConnector;
case inputTypes.SELECT:
return EnhancedSelectInput;

View File

@@ -0,0 +1,70 @@
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, { indexerFlags }) => indexerFlags,
(state) => state.settings.indexerFlags,
(selectedFlags, indexerFlags) => {
const value = [];
indexerFlags.items.forEach((item) => {
// eslint-disable-next-line no-bitwise
if ((selectedFlags & item.id) === item.id) {
value.push(item.id);
}
});
const values = indexerFlags.items.map(({ id, name }) => {
return {
key: id,
value: name
};
});
return {
value,
values
};
}
);
}
class IndexerFlagsSelectInputConnector extends Component {
onChange = ({ name, value }) => {
let indexerFlags = 0;
value.forEach((flagId) => {
indexerFlags += flagId;
});
this.props.onChange({ name, value: indexerFlags });
}
//
// Render
render() {
return (
<EnhancedSelectInput
{...this.props}
onChange={this.onChange}
/>
);
}
}
IndexerFlagsSelectInputConnector.propTypes = {
name: PropTypes.string.isRequired,
indexerFlags: PropTypes.number.isRequired,
value: PropTypes.arrayOf(PropTypes.number).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
onChange: PropTypes.func.isRequired
};
export default connect(createMapStateToProps)(IndexerFlagsSelectInputConnector);

View File

@@ -3,10 +3,17 @@ import React from 'react';
import TextInput from './TextInput';
import styles from './PasswordInput.css';
// Prevent a user from copying (or cutting) the password from the input
function onCopy(e) {
e.preventDefault();
e.nativeEvent.stopImmediatePropagation();
}
function PasswordInput(props) {
return (
<TextInput
{...props}
onCopy={onCopy}
/>
);
}

View File

@@ -130,7 +130,8 @@ class TextInput extends Component {
step,
min,
max,
onBlur
onBlur,
onCopy
} = this.props;
return (
@@ -155,6 +156,8 @@ class TextInput extends Component {
onChange={this.onChange}
onFocus={this.onFocus}
onBlur={onBlur}
onCopy={onCopy}
onCut={onCopy}
onKeyUp={this.onKeyUp}
onMouseDown={this.onMouseDown}
onMouseUp={this.onMouseUp}
@@ -180,6 +183,7 @@ TextInput.propTypes = {
onChange: PropTypes.func.isRequired,
onFocus: PropTypes.func,
onBlur: PropTypes.func,
onCopy: PropTypes.func,
onSelectionChange: PropTypes.func
};

View File

@@ -6,7 +6,7 @@ import { createSelector } from 'reselect';
import { saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
import { fetchMovies } from 'Store/Actions/movieActions';
import { fetchImportLists, fetchLanguages, fetchQualityProfiles, fetchUISettings } from 'Store/Actions/settingsActions';
import { fetchImportLists, fetchIndexerFlags, 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';
@@ -48,6 +48,7 @@ const selectIsPopulated = createSelector(
(state) => state.settings.ui.isPopulated,
(state) => state.settings.qualityProfiles.isPopulated,
(state) => state.settings.languages.isPopulated,
(state) => state.settings.indexerFlags.isPopulated,
(state) => state.settings.importLists.isPopulated,
(state) => state.system.status.isPopulated,
(
@@ -56,6 +57,7 @@ const selectIsPopulated = createSelector(
uiSettingsIsPopulated,
qualityProfilesIsPopulated,
languagesIsPopulated,
indexerFlagsIsPopulated,
importListsIsPopulated,
systemStatusIsPopulated
) => {
@@ -65,6 +67,7 @@ const selectIsPopulated = createSelector(
uiSettingsIsPopulated &&
qualityProfilesIsPopulated &&
languagesIsPopulated &&
indexerFlagsIsPopulated &&
importListsIsPopulated &&
systemStatusIsPopulated
);
@@ -77,6 +80,7 @@ const selectErrors = createSelector(
(state) => state.settings.ui.error,
(state) => state.settings.qualityProfiles.error,
(state) => state.settings.languages.error,
(state) => state.settings.indexerFlags.error,
(state) => state.settings.importLists.error,
(state) => state.system.status.error,
(
@@ -85,6 +89,7 @@ const selectErrors = createSelector(
uiSettingsError,
qualityProfilesError,
languagesError,
indexerFlagsError,
importListsError,
systemStatusError
) => {
@@ -94,6 +99,7 @@ const selectErrors = createSelector(
uiSettingsError ||
qualityProfilesError ||
languagesError ||
indexerFlagsError ||
importListsError ||
systemStatusError
);
@@ -105,6 +111,7 @@ const selectErrors = createSelector(
uiSettingsError,
qualityProfilesError,
languagesError,
indexerFlagsError,
importListsError,
systemStatusError
};
@@ -153,6 +160,9 @@ function createMapDispatchToProps(dispatch, props) {
dispatchFetchLanguages() {
dispatch(fetchLanguages());
},
dispatchFetchIndexerFlags() {
dispatch(fetchIndexerFlags());
},
dispatchFetchImportLists() {
dispatch(fetchImportLists());
},
@@ -191,6 +201,7 @@ class PageConnector extends Component {
this.props.dispatchFetchTags();
this.props.dispatchFetchQualityProfiles();
this.props.dispatchFetchLanguages();
this.props.dispatchFetchIndexerFlags();
this.props.dispatchFetchImportLists();
this.props.dispatchFetchUISettings();
this.props.dispatchFetchStatus();
@@ -215,6 +226,7 @@ class PageConnector extends Component {
dispatchFetchTags,
dispatchFetchQualityProfiles,
dispatchFetchLanguages,
dispatchFetchIndexerFlags,
dispatchFetchImportLists,
dispatchFetchUISettings,
dispatchFetchStatus,
@@ -254,6 +266,7 @@ PageConnector.propTypes = {
dispatchFetchTags: PropTypes.func.isRequired,
dispatchFetchQualityProfiles: PropTypes.func.isRequired,
dispatchFetchLanguages: PropTypes.func.isRequired,
dispatchFetchIndexerFlags: PropTypes.func.isRequired,
dispatchFetchImportLists: PropTypes.func.isRequired,
dispatchFetchUISettings: PropTypes.func.isRequired,
dispatchFetchStatus: PropTypes.func.isRequired,

View File

@@ -10,6 +10,7 @@ export const PASSWORD = 'password';
export const PATH = 'path';
export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect';
export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect';
export const SELECT = 'select';
export const TAG = 'tag';
export const TEXT = 'text';
@@ -30,6 +31,7 @@ export const all = [
PATH,
QUALITY_PROFILE_SELECT,
ROOT_FOLDER_SELECT,
INDEXER_FLAGS_SELECT,
SELECT,
TAG,
TEXT,

View File

@@ -549,7 +549,7 @@ class MovieDetails extends Component {
>
<span className={styles.sizeOnDisk}>
{
formatBytes(sizeOnDisk)
formatBytes(sizeOnDisk || 0)
}
</span>
</InfoLabel>

View File

@@ -72,7 +72,8 @@ class MovieIndexOverviews extends Component {
sortKey,
overviewOptions,
jumpToCharacter,
isMovieEditorActive
isMovieEditorActive,
isSmallScreen
} = this.props;
const {
@@ -82,13 +83,15 @@ class MovieIndexOverviews extends Component {
if (prevProps.sortKey !== sortKey ||
prevProps.overviewOptions !== overviewOptions) {
this.calculateGrid();
this.calculateGrid(this.state.width, isSmallScreen);
}
if (this._grid &&
if (
this._grid &&
(prevState.width !== width ||
prevState.rowHeight !== rowHeight ||
hasDifferentItemsOrOrder(prevProps.items, items) ||
prevProps.overviewOptions !== overviewOptions ||
prevProps.isMovieEditorActive !== isMovieEditorActive)) {
// recomputeGridSize also forces Grid to discard its cache of rendered cells
this._grid.recomputeGridSize();

View File

@@ -0,0 +1,37 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Modal from 'Components/Modal/Modal';
import FileEditModalContentConnector from './FileEditModalContentConnector';
class FileEditModal extends Component {
//
// Render
render() {
const {
isOpen,
onModalClose,
...otherProps
} = this.props;
return (
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
>
<FileEditModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
</Modal>
);
}
}
FileEditModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default FileEditModal;

View File

@@ -0,0 +1,237 @@
import PropTypes from 'prop-types';
import React, { Component } 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 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 translate from 'Utilities/String/translate';
class FileEditModalContent extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
const {
qualityId,
languageIds,
indexerFlags,
proper,
real,
edition,
releaseGroup
} = props;
this.state = {
qualityId,
languageIds,
indexerFlags,
proper,
real,
edition,
releaseGroup
};
}
//
// Listeners
onQualityChange = ({ value }) => {
this.setState({ qualityId: parseInt(value) });
}
onInputChange = ({ name, value }) => {
this.setState({ [name]: value });
}
onSaveInputs = () => {
this.props.onSaveInputs(this.state);
}
//
// Render
render() {
const {
isFetching,
isPopulated,
error,
qualities,
languages,
relativePath,
onModalClose
} = this.props;
const {
qualityId,
languageIds,
indexerFlags,
proper,
real,
edition,
releaseGroup
} = this.state;
const qualityOptions = qualities.map(({ id, name }) => {
return {
key: id,
value: name
};
});
const languageOptions = languages.map(({ id, name }) => {
return {
key: id,
value: name
};
});
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('EditMovieFile')} - {relativePath}
</ModalHeader>
<ModalBody>
{
isFetching &&
<LoadingIndicator />
}
{
!isFetching && !!error &&
<div>
{translate('UnableToLoadQualities')}
</div>
}
{
isPopulated && !error &&
<Form>
<FormGroup>
<FormLabel>{translate('Quality')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="quality"
value={qualityId}
values={qualityOptions}
onChange={this.onQualityChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Proper')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="proper"
value={proper}
onChange={this.onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Real')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="real"
value={real}
onChange={this.onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Languages')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="languageIds"
value={languageIds}
values={languageOptions}
onChange={this.onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('IndexerFlags')}</FormLabel>
<FormInputGroup
type={inputTypes.INDEXER_FLAGS_SELECT}
name="indexerFlags"
indexerFlags={indexerFlags}
onChange={this.onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Edition')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="edition"
value={edition}
onChange={this.onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('ReleaseGroup')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="releaseGroup"
value={releaseGroup}
onChange={this.onInputChange}
/>
</FormGroup>
</Form>
}
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>
{translate('Cancel')}
</Button>
<Button
kind={kinds.SUCCESS}
onPress={this.onSaveInputs}
>
{translate('Save')}
</Button>
</ModalFooter>
</ModalContent>
);
}
}
FileEditModalContent.propTypes = {
qualityId: PropTypes.number.isRequired,
proper: PropTypes.bool.isRequired,
real: PropTypes.bool.isRequired,
relativePath: PropTypes.string.isRequired,
edition: PropTypes.string.isRequired,
releaseGroup: PropTypes.string.isRequired,
languageIds: PropTypes.arrayOf(PropTypes.number).isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
indexerFlags: PropTypes.number.isRequired,
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
qualities: PropTypes.arrayOf(PropTypes.object).isRequired,
onSaveInputs: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default FileEditModalContent;

View File

@@ -0,0 +1,139 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { updateMovieFiles } from 'Store/Actions/movieFileActions';
import { fetchQualityProfileSchema } from 'Store/Actions/settingsActions';
import createMovieFileSelector from 'Store/Selectors/createMovieFileSelector';
import getQualities from 'Utilities/Quality/getQualities';
import FileEditModalContent from './FileEditModalContent';
function createMapStateToProps() {
return createSelector(
createMovieFileSelector(),
(state) => state.settings.qualityProfiles,
(state) => state.settings.languages,
(movieFile, qualityProfiles, languages) => {
const filterItems = ['Any', 'Original'];
const filteredLanguages = languages.items.filter((lang) => !filterItems.includes(lang.name));
const quality = movieFile.quality;
return {
isFetching: qualityProfiles.isSchemaFetching || languages.isFetching,
isPopulated: qualityProfiles.isSchemaPopulated && languages.isPopulated,
error: qualityProfiles.error || languages.error,
qualityId: quality ? quality.quality.id : 0,
real: quality ? quality.revision.real > 0 : false,
proper: quality ? quality.revision.version > 1 : false,
qualities: getQualities(qualityProfiles.schema.items),
languageIds: movieFile.languages ? movieFile.languages.map((l) => l.id) : [],
languages: filteredLanguages,
indexerFlags: movieFile.indexerFlags,
edition: movieFile.edition,
releaseGroup: movieFile.releaseGroup,
relativePath: movieFile.relativePath
};
}
);
}
const mapDispatchToProps = {
dispatchFetchQualityProfileSchema: fetchQualityProfileSchema,
dispatchUpdateMovieFiles: updateMovieFiles
};
class FileEditModalContentConnector extends Component {
//
// Lifecycle
componentDidMount = () => {
if (!this.props.isPopulated) {
this.props.dispatchFetchQualityProfileSchema();
}
}
//
// Listeners
onSaveInputs = ( payload ) => {
const {
qualityId,
real,
proper,
languageIds,
edition,
releaseGroup,
indexerFlags
} = payload;
const quality = this.props.qualities.find((item) => item.id === qualityId);
const languages = [];
languageIds.forEach((languageId) => {
const language = this.props.languages.find((item) => item.id === parseInt(languageId));
if (language !== undefined) {
languages.push(language);
}
});
const revision = {
version: proper ? 2 : 1,
real: real ? 1 : 0
};
const movieFileIds = [this.props.movieFileId];
this.props.dispatchUpdateMovieFiles({
movieFileIds,
languages,
indexerFlags,
edition,
releaseGroup,
quality: {
quality,
revision
}
});
this.props.onModalClose(true);
}
//
// Render
render() {
return (
<FileEditModalContent
{...this.props}
onSaveInputs={this.onSaveInputs}
/>
);
}
}
FileEditModalContentConnector.propTypes = {
movieFileId: PropTypes.number.isRequired,
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
qualities: PropTypes.arrayOf(PropTypes.object).isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
languageIds: PropTypes.arrayOf(PropTypes.number).isRequired,
indexerFlags: PropTypes.number.isRequired,
qualityId: PropTypes.number.isRequired,
real: PropTypes.bool.isRequired,
edition: PropTypes.string.isRequired,
releaseGroup: PropTypes.string.isRequired,
relativePath: PropTypes.string.isRequired,
proper: PropTypes.bool.isRequired,
dispatchFetchQualityProfileSchema: PropTypes.func.isRequired,
dispatchUpdateMovieFiles: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(FileEditModalContentConnector);

View File

@@ -3,16 +3,14 @@ import React, { Component } from 'react';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRowCellButton from 'Components/Table/Cells/TableRowCellButton';
import TableRow from 'Components/Table/TableRow';
import { icons, kinds } from 'Helpers/Props';
import MovieFormats from 'Movie/MovieFormats';
import MovieLanguage from 'Movie/MovieLanguage';
import MovieQuality from 'Movie/MovieQuality';
import SelectLanguageModal from 'MovieFile/Language/SelectLanguageModal';
import FileEditModal from 'MovieFile/Edit/FileEditModal';
import MediaInfoConnector from 'MovieFile/MediaInfoConnector';
import * as mediaInfoTypes from 'MovieFile/mediaInfoTypes';
import SelectQualityModal from 'MovieFile/Quality/SelectQualityModal';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import FileDetailsModal from '../FileDetailsModal';
@@ -28,32 +26,15 @@ class MovieFileEditorRow extends Component {
super(props, context);
this.state = {
isSelectQualityModalOpen: false,
isSelectLanguageModalOpen: false,
isConfirmDeleteModalOpen: false,
isFileDetailsModalOpen: false
isFileDetailsModalOpen: false,
isFileEditModalOpen: false
};
}
//
// Listeners
onSelectQualityPress = () => {
this.setState({ isSelectQualityModalOpen: true });
}
onSelectLanguagePress = () => {
this.setState({ isSelectLanguageModalOpen: true });
}
onSelectQualityModalClose = () => {
this.setState({ isSelectQualityModalOpen: false });
}
onSelectLanguageModalClose = () => {
this.setState({ isSelectLanguageModalOpen: false });
}
onDeletePress = () => {
this.setState({ isConfirmDeleteModalOpen: true });
}
@@ -76,6 +57,14 @@ class MovieFileEditorRow extends Component {
this.setState({ isFileDetailsModalOpen: false });
}
onFileEditPress = () => {
this.setState({ isFileEditModalOpen: true });
}
onFileEditModalClose = () => {
this.setState({ isFileEditModalOpen: false });
}
//
// Render
@@ -92,9 +81,8 @@ class MovieFileEditorRow extends Component {
} = this.props;
const {
isSelectQualityModalOpen,
isSelectLanguageModalOpen,
isFileDetailsModalOpen,
isFileEditModalOpen,
isConfirmDeleteModalOpen
} = this.state;
@@ -132,10 +120,8 @@ class MovieFileEditorRow extends Component {
{formatBytes(size)}
</TableRowCell>
<TableRowCellButton
<TableRowCell
className={styles.language}
title={translate('ClickToChangeLanguage')}
onPress={this.onSelectLanguagePress}
>
{
showLanguagePlaceholder &&
@@ -149,12 +135,10 @@ class MovieFileEditorRow extends Component {
languages={languages}
/>
}
</TableRowCellButton>
</TableRowCell>
<TableRowCellButton
<TableRowCell
className={styles.quality}
title={translate('ClickToChangeQuality')}
onPress={this.onSelectQualityPress}
>
{
showQualityPlaceholder &&
@@ -169,7 +153,7 @@ class MovieFileEditorRow extends Component {
isCutoffNotMet={qualityCutoffNotMet}
/>
}
</TableRowCellButton>
</TableRowCell>
<TableRowCell
className={styles.formats}
@@ -180,6 +164,11 @@ class MovieFileEditorRow extends Component {
</TableRowCell>
<TableRowCell className={styles.actions}>
<IconButton
name={icons.EDIT}
onPress={this.onFileEditPress}
/>
<IconButton
name={icons.MEDIA_INFO}
onPress={this.onFileDetailsPress}
@@ -198,6 +187,12 @@ class MovieFileEditorRow extends Component {
mediaInfo={mediaInfo}
/>
<FileEditModal
movieFileId={id}
isOpen={isFileEditModalOpen}
onModalClose={this.onFileEditModalClose}
/>
<ConfirmModal
isOpen={isConfirmDeleteModalOpen}
ids={[id]}
@@ -208,22 +203,6 @@ class MovieFileEditorRow extends Component {
onConfirm={this.onConfirmDelete}
onCancel={this.onConfirmDeleteModalClose}
/>
<SelectQualityModal
isOpen={isSelectQualityModalOpen}
ids={[id]}
qualityId={quality ? quality.quality.id : 0}
proper={quality ? quality.revision.version > 1 : false}
real={quality ? quality.revision.real > 0 : false}
onModalClose={this.onSelectQualityModalClose}
/>
<SelectLanguageModal
isOpen={isSelectLanguageModalOpen}
ids={[id]}
languageIds={languages ? languages.map((l) => l.id) : []}
onModalClose={this.onSelectLanguageModalClose}
/>
</TableRow>
);
}

View File

@@ -49,6 +49,12 @@ function EditImportListModalContent(props) {
fields
} = item;
const importListTypeOptions = [
{ key: 'manual', value: 'Manual' },
{ key: 'automatic', value: 'Automatic Add' },
{ key: 'exclusion', value: 'Exclusion List' }
];
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
@@ -100,8 +106,9 @@ function EditImportListModalContent(props) {
<FormLabel>{translate('EnableAutomaticAdd')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
type={inputTypes.SELECT}
name="enableAuto"
values={importListTypeOptions}
helpText={translate('EnableAutoHelpText')}
{...enableAuto}
onChange={onInputChange}
@@ -173,7 +180,7 @@ function EditImportListModalContent(props) {
<FormInputGroup
type={inputTypes.TAG}
name="tags"
helpText={translate('TagsHelpText')}
helpText={translate('ListTagsHelpText')}
{...tags}
onChange={onInputChange}
/>

View File

@@ -1,10 +1,12 @@
import $ from 'jquery';
import _ from 'lodash';
import { batchActions } from 'redux-batched-actions';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import getProviderState from 'Utilities/State/getProviderState';
import { set, updateItem } from '../baseActions';
const abortCurrentRequests = {};
let lastSaveData = null;
export function createCancelSaveProviderHandler(section) {
return function(getState, payload, dispatch) {
@@ -26,25 +28,33 @@ function createSaveProviderHandler(section, url, options = {}) {
} = payload;
const saveData = getProviderState({ id, ...otherPayload }, getState, section);
const requestUrl = id ? `${url}/${id}` : url;
const params = { ...queryParams };
// If the user is re-saving the same provider without changes
// force it to be saved. Only applies to editing existing providers.
if (id && _.isEqual(saveData, lastSaveData)) {
params.forceSave = true;
}
lastSaveData = saveData;
const ajaxOptions = {
url: `${url}?${$.param(queryParams, true)}`,
method: 'POST',
url: `${requestUrl}?${$.param(params, true)}`,
method: id ? 'PUT' : 'POST',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify(saveData)
};
if (id) {
ajaxOptions.url = `${url}/${id}?${$.param(queryParams, true)}`;
ajaxOptions.method = 'PUT';
}
const { request, abortRequest } = createAjaxRequest(ajaxOptions);
abortCurrentRequests[section] = abortRequest;
request.done((data) => {
lastSaveData = null;
dispatch(batchActions([
updateItem({ section, ...data }),

View File

@@ -0,0 +1,48 @@
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
import { createThunk } from 'Store/thunks';
//
// Variables
const section = 'settings.indexerFlags';
//
// Actions Types
export const FETCH_INDEXER_FLAGS = 'settings/indexerFlags/fetchIndexerFlags';
//
// Action Creators
export const fetchIndexerFlags = createThunk(FETCH_INDEXER_FLAGS);
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
items: []
},
//
// Action Handlers
actionHandlers: {
[FETCH_INDEXER_FLAGS]: createFetchHandler(section, '/indexerFlag')
},
//
// Reducers
reducers: {
}
};

View File

@@ -1,8 +1,11 @@
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import { sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
import translate from 'Utilities/String/translate';
import { set, updateItem } from './baseActions';
import createHandleActions from './Creators/createHandleActions';
import createRemoveItemHandler from './Creators/createRemoveItemHandler';
import createServerSideCollectionHandlers from './Creators/createServerSideCollectionHandlers';
@@ -25,6 +28,7 @@ export const defaultState = {
sortDirection: sortDirections.DESCENDING,
error: null,
items: [],
isRemoving: false,
columns: [
{
@@ -96,7 +100,8 @@ export const GOTO_LAST_BLACKLIST_PAGE = 'blacklist/gotoBlacklistLastPage';
export const GOTO_BLACKLIST_PAGE = 'blacklist/gotoBlacklistPage';
export const SET_BLACKLIST_SORT = 'blacklist/setBlacklistSort';
export const SET_BLACKLIST_TABLE_OPTION = 'blacklist/setBlacklistTableOption';
export const REMOVE_FROM_BLACKLIST = 'blacklist/removeFromBlacklist';
export const REMOVE_BLACKLIST_ITEM = 'blacklist/removeBlacklistItem';
export const REMOVE_BLACKLIST_ITEMS = 'blacklist/removeBlacklistItems';
export const CLEAR_BLACKLIST = 'blacklist/clearBlacklist';
//
@@ -110,7 +115,8 @@ export const gotoBlacklistLastPage = createThunk(GOTO_LAST_BLACKLIST_PAGE);
export const gotoBlacklistPage = createThunk(GOTO_BLACKLIST_PAGE);
export const setBlacklistSort = createThunk(SET_BLACKLIST_SORT);
export const setBlacklistTableOption = createAction(SET_BLACKLIST_TABLE_OPTION);
export const removeFromBlacklist = createThunk(REMOVE_FROM_BLACKLIST);
export const removeBlacklistItem = createThunk(REMOVE_BLACKLIST_ITEM);
export const removeBlacklistItems = createThunk(REMOVE_BLACKLIST_ITEMS);
export const clearBlacklist = createAction(CLEAR_BLACKLIST);
//
@@ -131,7 +137,53 @@ export const actionHandlers = handleThunks({
[serverSideCollectionHandlers.SORT]: SET_BLACKLIST_SORT
}),
[REMOVE_FROM_BLACKLIST]: createRemoveItemHandler(section, '/blacklist')
[REMOVE_BLACKLIST_ITEM]: createRemoveItemHandler(section, '/blacklist'),
[REMOVE_BLACKLIST_ITEMS]: function(getState, payload, dispatch) {
const {
ids
} = payload;
dispatch(batchActions([
...ids.map((id) => {
return updateItem({
section,
id,
isRemoving: true
});
}),
set({ section, isRemoving: true })
]));
const promise = createAjaxRequest({
url: '/blacklist/bulk',
method: 'DELETE',
dataType: 'json',
data: JSON.stringify({ ids })
}).request;
promise.done((data) => {
// Don't use batchActions with thunks
dispatch(fetchBlacklist());
dispatch(set({ section, isRemoving: false }));
});
promise.fail((xhr) => {
dispatch(batchActions([
...ids.map((id) => {
return updateItem({
section,
id,
isRemoving: false
});
}),
set({ section, isRemoving: false })
]));
});
}
});
//

View File

@@ -141,7 +141,10 @@ export const actionHandlers = handleThunks({
const {
movieFileIds,
languages,
quality
indexerFlags,
quality,
edition,
releaseGroup
} = payload;
dispatch(set({ section, isSaving: true }));
@@ -154,10 +157,22 @@ export const actionHandlers = handleThunks({
data.languages = languages;
}
if (indexerFlags !== undefined) {
data.indexerFlags = indexerFlags;
}
if (quality) {
data.quality = quality;
}
if (releaseGroup) {
data.releaseGroup = releaseGroup;
}
if (edition) {
data.edition = edition;
}
const promise = createAjaxRequest({
url: '/movieFile/editor',
method: 'PUT',
@@ -174,10 +189,22 @@ export const actionHandlers = handleThunks({
props.languages = languages;
}
if (indexerFlags) {
props.indexerFlags = indexerFlags;
}
if (quality) {
props.quality = quality;
}
if (edition) {
props.edition = edition;
}
if (releaseGroup) {
props.releaseGroup = releaseGroup;
}
return updateItem({ section, id, ...props });
}),

View File

@@ -314,9 +314,9 @@ export const actionHandlers = handleThunks({
}).request;
promise.done((data) => {
dispatch(batchActions([
fetchQueue(),
dispatch(fetchQueue());
dispatch(batchActions([
...ids.map((id) => {
return updateItem({
section: paged,
@@ -400,10 +400,10 @@ export const actionHandlers = handleThunks({
}).request;
promise.done((data) => {
dispatch(batchActions([
set({ section: paged, isRemoving: false }),
fetchQueue()
]));
// Don't use batchActions with thunks
dispatch(fetchQueue());
dispatch(set({ section: paged, isRemoving: false }));
});
promise.fail((xhr) => {

View File

@@ -10,6 +10,7 @@ import general from './Settings/general';
import importExclusions from './Settings/importExclusions';
import importListOptions from './Settings/importListOptions';
import importLists from './Settings/importLists';
import indexerFlags from './Settings/indexerFlags';
import indexerOptions from './Settings/indexerOptions';
import indexers from './Settings/indexers';
import languages from './Settings/languages';
@@ -31,6 +32,7 @@ export * from './Settings/delayProfiles';
export * from './Settings/downloadClients';
export * from './Settings/downloadClientOptions';
export * from './Settings/general';
export * from './Settings/indexerFlags';
export * from './Settings/indexerOptions';
export * from './Settings/indexers';
export * from './Settings/languages';
@@ -66,6 +68,7 @@ export const defaultState = {
downloadClients: downloadClients.defaultState,
downloadClientOptions: downloadClientOptions.defaultState,
general: general.defaultState,
indexerFlags: indexerFlags.defaultState,
indexerOptions: indexerOptions.defaultState,
indexers: indexers.defaultState,
languages: languages.defaultState,
@@ -109,6 +112,7 @@ export const actionHandlers = handleThunks({
...downloadClients.actionHandlers,
...downloadClientOptions.actionHandlers,
...general.actionHandlers,
...indexerFlags.actionHandlers,
...indexerOptions.actionHandlers,
...indexers.actionHandlers,
...languages.actionHandlers,
@@ -143,6 +147,7 @@ export const reducers = createHandleActions({
...downloadClients.reducers,
...downloadClientOptions.reducers,
...general.reducers,
...indexerFlags.reducers,
...indexerOptions.reducers,
...indexers.reducers,
...languages.reducers,

View File

@@ -19,6 +19,7 @@ function getInternalLink(source) {
case 'IndexerRssCheck':
case 'IndexerSearchCheck':
case 'IndexerStatusCheck':
case 'IndexerLongTermStatusCheck':
return (
<IconButton
name={icons.SETTINGS}

View File

@@ -4,6 +4,7 @@ import isInNextWeek from 'Utilities/Date/isInNextWeek';
import isToday from 'Utilities/Date/isToday';
import isTomorrow from 'Utilities/Date/isTomorrow';
import isYesterday from 'Utilities/Date/isYesterday';
import translate from 'Utilities/String/translate';
function getRelativeDate(date, shortDateFormat, showRelativeDates, { timeFormat, includeSeconds = false, timeForToday = false } = {}) {
if (!date) {
@@ -21,15 +22,15 @@ function getRelativeDate(date, shortDateFormat, showRelativeDates, { timeFormat,
}
if (isYesterday(date)) {
return 'Yesterday';
return translate('Yesterday');
}
if (isTodayDate) {
return 'Today';
return translate('Today');
}
if (isTomorrow(date)) {
return 'Tomorrow';
return translate('Tomorrow');
}
if (isInNextWeek(date)) {

View File

@@ -1,11 +1,12 @@
import moment from 'moment';
function isToday(date) {
if (!date) {
return false;
}
return moment(date).isSame(moment(), 'day');
const dateObj = (typeof date === 'object') ? date : new Date(date);
const today = new Date();
return dateObj.getDate() === today.getDate() && dateObj.getMonth() === today.getMonth() && dateObj.getFullYear() === today.getFullYear();
}
export default isToday;

View File

@@ -1,11 +1,13 @@
import moment from 'moment';
function isTomorrow(date) {
if (!date) {
return false;
}
return moment(date).isSame(moment().add(1, 'day'), 'day');
const dateObj = (typeof date === 'object') ? date : new Date(date);
const today = new Date();
const tomorrow = new Date((today.setDate(today.getDate() + 1)));
return dateObj.getDate() === tomorrow.getDate() && dateObj.getMonth() === tomorrow.getMonth() && dateObj.getFullYear() === tomorrow.getFullYear();
}
export default isTomorrow;

View File

@@ -1,11 +1,13 @@
import moment from 'moment';
function isYesterday(date) {
if (!date) {
return false;
}
return moment(date).isSame(moment().subtract(1, 'day'), 'day');
const dateObj = (typeof date === 'object') ? date : new Date(date);
const today = new Date();
const yesterday = new Date((today.setDate(today.getDate() - 1)));
return dateObj.getDate() === yesterday.getDate() && dateObj.getMonth() === yesterday.getMonth() && dateObj.getFullYear() === yesterday.getFullYear();
}
export default isYesterday;

View File

@@ -37,13 +37,14 @@ Compression=lzma2/normal
AppContact={#ForumsURL}
VersionInfoVersion={#BaseVersion}.{#BuildNumber}
SetupLogging=yes
OutputDir=output
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopIcon"; Description: "{cm:CreateDesktopIcon}"
Name: "windowsService"; Description: "Install Windows Service (Starts when the computer starts)"; GroupDescription: "Start automatically"; Flags: exclusive
Name: "windowsService"; Description: "Install Windows Service (Starts when the computer starts as the LocalService user, you will need to change the user to access network shares)"; GroupDescription: "Start automatically"; Flags: exclusive
Name: "startupShortcut"; Description: "Create shortcut in Startup folder (Starts when you log into Windows)"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
Name: "none"; Description: "Do not start automatically"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
@@ -57,6 +58,9 @@ Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Parameters: "/icon"
Name: "{commondesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Parameters: "/icon"; Tasks: desktopIcon
Name: "{userstartup}\{#AppName}"; Filename: "{app}\Radarr.exe"; WorkingDir: "{app}"; Tasks: startupShortcut
[InstallDelete]
Name: "{app}"; Type: filesandordirs
[Run]
Filename: "{app}\Radarr.Console.exe"; StatusMsg: "Removing previous Windows Service"; Parameters: "/u"; Flags: runhidden waituntilterminated;
Filename: "{app}\Radarr.Console.exe"; Description: "Enable Access from Other Devices"; StatusMsg: "Enabling Remote access"; Parameters: "/registerurl"; Flags: postinstall runascurrentuser runhidden waituntilterminated; Tasks: startupShortcut none;
@@ -72,5 +76,6 @@ function PrepareToInstall(var NeedsRestart: Boolean): String;
var
ResultCode: Integer;
begin
Exec(ExpandConstant('{commonappdata}\Radarr\bin\Radarr.Console.exe'), '/u', '', 0, ewWaitUntilTerminated, ResultCode)
Exec('net', 'stop radarr', '', 0, ewWaitUntilTerminated, ResultCode)
Exec('sc', 'delete radarr', '', 0, ewWaitUntilTerminated, ResultCode)
end;

View File

@@ -21,7 +21,7 @@ namespace NzbDrone.Api.ImportList
base.MapToResource(resource, definition);
resource.Enabled = definition.Enabled;
resource.EnableAuto = definition.EnableAuto;
resource.EnableAuto = (int)definition.EnableAuto;
resource.ProfileId = definition.ProfileId;
resource.RootFolderPath = definition.RootFolderPath;
resource.ShouldMonitor = definition.ShouldMonitor;
@@ -34,7 +34,7 @@ namespace NzbDrone.Api.ImportList
base.MapToModel(definition, resource);
definition.Enabled = resource.Enabled;
definition.EnableAuto = resource.EnableAuto;
definition.EnableAuto = (ImportListType)resource.EnableAuto;
definition.ProfileId = resource.ProfileId;
definition.RootFolderPath = resource.RootFolderPath;
definition.ShouldMonitor = resource.ShouldMonitor;

View File

@@ -6,7 +6,7 @@ namespace NzbDrone.Api.ImportList
public class ImportListResource : ProviderResource
{
public bool Enabled { get; set; }
public bool EnableAuto { get; set; }
public int EnableAuto { get; set; }
public bool ShouldMonitor { get; set; }
public string RootFolderPath { get; set; }
public int ProfileId { get; set; }

View File

@@ -42,6 +42,8 @@ namespace NzbDrone.Automation.Test
// Timeout as windows automation tests seem to take alot longer to get going
driver = new ChromeDriver(service, options, new TimeSpan(0, 3, 0));
driver.Manage().Window.Size = new System.Drawing.Size(1920, 1080);
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger());
_runner.KillAll();
_runner.Start();
@@ -62,6 +64,19 @@ namespace NzbDrone.Automation.Test
.Select(e => e.Text);
}
protected void TakeScreenshot(string name)
{
try
{
Screenshot image = ((ITakesScreenshot)driver).GetScreenshot();
image.SaveAsFile($"./{name}_test_screenshot.png", ScreenshotImageFormat.Png);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to save screenshot {name}, {ex.Message}");
}
}
[OneTimeTearDown]
public void SmokeTestTearDown()
{

View File

@@ -1,4 +1,5 @@
using FluentAssertions;
using System.Reflection;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Automation.Test.PageModel;
using OpenQA.Selenium;
@@ -21,6 +22,10 @@ namespace NzbDrone.Automation.Test
{
_page.MovieNavIcon.Click();
_page.WaitForNoSpinner();
var imageName = MethodBase.GetCurrentMethod().Name;
TakeScreenshot(imageName);
_page.Find(By.CssSelector("div[class*='MovieIndex']")).Should().NotBeNull();
}
@@ -30,6 +35,9 @@ namespace NzbDrone.Automation.Test
_page.CalendarNavIcon.Click();
_page.WaitForNoSpinner();
var imageName = MethodBase.GetCurrentMethod().Name;
TakeScreenshot(imageName);
_page.Find(By.CssSelector("div[class*='CalendarPage']")).Should().NotBeNull();
}
@@ -39,6 +47,9 @@ namespace NzbDrone.Automation.Test
_page.ActivityNavIcon.Click();
_page.WaitForNoSpinner();
var imageName = MethodBase.GetCurrentMethod().Name;
TakeScreenshot(imageName);
_page.Find(By.LinkText("Queue")).Should().NotBeNull();
_page.Find(By.LinkText("History")).Should().NotBeNull();
_page.Find(By.LinkText("Blacklist")).Should().NotBeNull();
@@ -50,6 +61,9 @@ namespace NzbDrone.Automation.Test
_page.SystemNavIcon.Click();
_page.WaitForNoSpinner();
var imageName = MethodBase.GetCurrentMethod().Name;
TakeScreenshot(imageName);
_page.Find(By.CssSelector("div[class*='Health']")).Should().NotBeNull();
}
@@ -58,11 +72,12 @@ namespace NzbDrone.Automation.Test
{
_page.MovieNavIcon.Click();
_page.WaitForNoSpinner();
_page.Find(By.LinkText("Add New")).Click();
_page.WaitForNoSpinner();
var imageName = MethodBase.GetCurrentMethod().Name;
TakeScreenshot(imageName);
_page.Find(By.CssSelector("input[class*='AddNewMovie-searchInput']")).Should().NotBeNull();
}
}

View File

@@ -8,7 +8,7 @@
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
<PackageReference Include="NunitXml.TestLogger" Version="2.1.41" />
<PackageReference Include="Selenium.Support" Version="3.141.0" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="84.0.4147.3001" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="86.0.4240.2200" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Test.Common\Radarr.Test.Common.csproj" />

View File

@@ -731,7 +731,7 @@ namespace NzbDrone.Common.Test.DiskTests
// Note: never returns anything.
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.GetFileInfos(It.IsAny<string>()))
.Setup(v => v.GetFileInfos(It.IsAny<string>(), SearchOption.TopDirectoryOnly))
.Returns(new List<FileInfo>());
Mocker.GetMock<IDiskProvider>()
@@ -765,8 +765,8 @@ namespace NzbDrone.Common.Test.DiskTests
.Returns<string>(v => new DirectoryInfo(v).GetDirectories().ToList());
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.GetFileInfos(It.IsAny<string>()))
.Returns<string>(v => new DirectoryInfo(v).GetFiles().ToList());
.Setup(v => v.GetFileInfos(It.IsAny<string>(), SearchOption.TopDirectoryOnly))
.Returns<string, SearchOption>((v, _) => new DirectoryInfo(v).GetFiles().ToList());
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.GetFileSize(It.IsAny<string>()))

View File

@@ -8,6 +8,8 @@ namespace NzbDrone.Common.Test.Http
public class HttpUriFixture : TestBase
{
[TestCase("abc://my_host.com:8080/root/api/")]
[TestCase("abc://my_host.com:8080//root/api/")]
[TestCase("abc://my_host.com:8080/root//api/")]
public void should_parse(string uri)
{
var newUri = new HttpUri(uri);

View File

@@ -505,13 +505,20 @@ namespace NzbDrone.Common.Disk
return di.GetDirectories().ToList();
}
public List<FileInfo> GetFileInfos(string path)
public FileInfo GetFileInfo(string path)
{
Ensure.That(path, () => path).IsValidPath();
return new FileInfo(path);
}
public List<FileInfo> GetFileInfos(string path, SearchOption searchOption = SearchOption.TopDirectoryOnly)
{
Ensure.That(path, () => path).IsValidPath();
var di = new DirectoryInfo(path);
return di.GetFiles().ToList();
return di.GetFiles("*", searchOption).ToList();
}
public void RemoveEmptySubfolders(string path)

View File

@@ -52,7 +52,8 @@ namespace NzbDrone.Common.Disk
List<IMount> GetMounts();
IMount GetMount(string path);
List<DirectoryInfo> GetDirectoryInfos(string path);
List<FileInfo> GetFileInfos(string path);
FileInfo GetFileInfo(string path);
List<FileInfo> GetFileInfos(string path, SearchOption searchOption = SearchOption.TopDirectoryOnly);
void RemoveEmptySubfolders(string path);
void SaveStream(Stream stream, string path);
bool IsValidFilePermissionMask(string mask);

View File

@@ -8,7 +8,7 @@ namespace NzbDrone.Common.Http
{
public class HttpUri : IEquatable<HttpUri>
{
private static readonly Regex RegexUri = new Regex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-_A-Z0-9.]+)(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/)[^/?#\r\n]+)+/?|/)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex RegexUri = new Regex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-_A-Z0-9.]+)(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/+)[^/?#\r\n]+)+/*|/+)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private readonly string _uri;
public string FullUri => _uri;

View File

@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.HealthCheck.Checks
{
[TestFixture]
public class IndexerLongTermStatusCheckFixture : CoreTest<IndexerLongTermStatusCheck>
{
private List<IIndexer> _indexers = new List<IIndexer>();
private List<IndexerStatus> _blockedIndexers = new List<IndexerStatus>();
[SetUp]
public void SetUp()
{
Mocker.GetMock<IIndexerFactory>()
.Setup(v => v.GetAvailableProviders())
.Returns(_indexers);
Mocker.GetMock<IIndexerStatusService>()
.Setup(v => v.GetBlockedProviders())
.Returns(_blockedIndexers);
Mocker.GetMock<ILocalizationService>()
.Setup(v => v.GetLocalizedString(It.IsAny<string>()))
.Returns("Error");
}
private Mock<IIndexer> GivenIndexer(int id, double backoffHours, double failureHours)
{
var mockIndexer = new Mock<IIndexer>();
mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition { Id = id });
mockIndexer.SetupGet(s => s.SupportsSearch).Returns(true);
_indexers.Add(mockIndexer.Object);
if (backoffHours != 0.0)
{
_blockedIndexers.Add(new IndexerStatus
{
ProviderId = id,
InitialFailure = DateTime.UtcNow.AddHours(-failureHours),
MostRecentFailure = DateTime.UtcNow.AddHours(-0.1),
EscalationLevel = 5,
DisabledTill = DateTime.UtcNow.AddHours(backoffHours)
});
}
return mockIndexer;
}
[Test]
public void should_not_return_error_when_no_indexers()
{
Subject.Check().ShouldBeOk();
}
[Test]
public void should_return_warning_if_indexer_unavailable()
{
GivenIndexer(1, 10.0, 24.0);
GivenIndexer(2, 0.0, 0.0);
Subject.Check().ShouldBeWarning();
}
[Test]
public void should_return_error_if_all_indexers_unavailable()
{
GivenIndexer(1, 10.0, 24.0);
Subject.Check().ShouldBeError();
}
[Test]
public void should_return_warning_if_few_indexers_unavailable()
{
GivenIndexer(1, 10.0, 24.0);
GivenIndexer(2, 10.0, 24.0);
GivenIndexer(3, 0.0, 0.0);
Subject.Check().ShouldBeWarning();
}
}
}

View File

@@ -65,7 +65,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
[Test]
public void should_return_warning_if_indexer_unavailable()
{
GivenIndexer(1, 10.0, 24.0);
GivenIndexer(1, 2.0, 4.0);
GivenIndexer(2, 0.0, 0.0);
Subject.Check().ShouldBeWarning();
@@ -74,7 +74,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
[Test]
public void should_return_error_if_all_indexers_unavailable()
{
GivenIndexer(1, 10.0, 24.0);
GivenIndexer(1, 2.0, 4.0);
Subject.Check().ShouldBeError();
}
@@ -82,8 +82,8 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
[Test]
public void should_return_warning_if_few_indexers_unavailable()
{
GivenIndexer(1, 10.0, 24.0);
GivenIndexer(2, 10.0, 24.0);
GivenIndexer(1, 2.0, 4.0);
GivenIndexer(2, 2.0, 4.0);
GivenIndexer(3, 0.0, 0.0);
Subject.Check().ShouldBeWarning();

View File

@@ -37,7 +37,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
var mockList = new Mock<IImportList>();
mockList.SetupGet(s => s.Definition).Returns(new ImportListDefinition { Id = id });
mockList.SetupGet(s => s.EnableAuto).Returns(true);
mockList.SetupGet(s => s.EnableAuto).Returns(ImportListType.Automatic);
_lists.Add(mockList.Object);

View File

@@ -42,12 +42,12 @@ namespace NzbDrone.Core.Test.ImportListTests
.Returns<Movie>(m => new Movie { TmdbId = m.TmdbId });
}
private void GivenList(int id, bool enabled, bool enabledAuto, ImportListFetchResult fetchResult)
private void GivenList(int id, bool enabled, ImportListType enabledAuto, ImportListFetchResult fetchResult)
{
CreateListResult(id, enabled, enabledAuto, fetchResult);
}
private Mock<IImportList> CreateListResult(int id, bool enabled, bool enabledAuto, ImportListFetchResult fetchResult)
private Mock<IImportList> CreateListResult(int id, bool enabled, ImportListType enabledAuto, ImportListFetchResult fetchResult)
{
var importListDefinition = new ImportListDefinition { Id = id, EnableAuto = enabledAuto };
@@ -71,7 +71,7 @@ namespace NzbDrone.Core.Test.ImportListTests
public void should_return_failure_if_blocked_list()
{
var fetchResult = new ImportListFetchResult();
GivenList(1, true, true, fetchResult);
GivenList(1, true, ImportListType.Automatic, fetchResult);
GivenBlockedList(1);
var listResult = Subject.Fetch();
@@ -82,11 +82,11 @@ namespace NzbDrone.Core.Test.ImportListTests
public void should_return_failure_if_one_blocked_list_one_good_list()
{
var fetchResult1 = new ImportListFetchResult();
GivenList(1, true, true, fetchResult1);
GivenList(1, true, ImportListType.Automatic, fetchResult1);
GivenBlockedList(1);
var fetchResult2 = new ImportListFetchResult { Movies = _listMovies, AnyFailure = true };
GivenList(2, true, true, fetchResult2);
GivenList(2, true, ImportListType.Automatic, fetchResult2);
var listResult = Subject.Fetch();
listResult.AnyFailure.Should().BeTrue();
@@ -96,7 +96,7 @@ namespace NzbDrone.Core.Test.ImportListTests
public void should_return_failure_if_single_list_fails()
{
var fetchResult = new ImportListFetchResult { Movies = _listMovies, AnyFailure = true };
GivenList(1, true, true, fetchResult);
GivenList(1, true, ImportListType.Automatic, fetchResult);
var listResult = Subject.Fetch();
listResult.AnyFailure.Should().BeTrue();
@@ -106,9 +106,9 @@ namespace NzbDrone.Core.Test.ImportListTests
public void should_return_failure_if_any_list_fails()
{
var fetchResult1 = new ImportListFetchResult { Movies = _listMovies, AnyFailure = true };
GivenList(1, true, true, fetchResult1);
GivenList(1, true, ImportListType.Automatic, fetchResult1);
var fetchResult2 = new ImportListFetchResult { Movies = _listMovies, AnyFailure = false };
GivenList(2, true, true, fetchResult2);
GivenList(2, true, ImportListType.Automatic, fetchResult2);
var listResult = Subject.Fetch();
listResult.AnyFailure.Should().BeTrue();
@@ -131,7 +131,7 @@ namespace NzbDrone.Core.Test.ImportListTests
{
var listId = 1;
var fetchResult = new ImportListFetchResult { Movies = _listMovies, AnyFailure = false };
GivenList(listId, true, true, fetchResult);
GivenList(listId, true, ImportListType.Automatic, fetchResult);
var listResult = Subject.Fetch();
listResult.AnyFailure.Should().BeFalse();
@@ -145,7 +145,7 @@ namespace NzbDrone.Core.Test.ImportListTests
{
var listId = 1;
var fetchResult = new ImportListFetchResult { Movies = _listMovies, AnyFailure = true };
GivenList(listId, true, true, fetchResult);
GivenList(listId, true, ImportListType.Automatic, fetchResult);
var listResult = Subject.Fetch();
listResult.AnyFailure.Should().BeTrue();
@@ -159,10 +159,10 @@ namespace NzbDrone.Core.Test.ImportListTests
{
var passedListId = 1;
var fetchResult1 = new ImportListFetchResult { Movies = _listMovies, AnyFailure = false };
GivenList(passedListId, true, true, fetchResult1);
GivenList(passedListId, true, ImportListType.Automatic, fetchResult1);
var failedListId = 2;
var fetchResult2 = new ImportListFetchResult { Movies = _listMovies, AnyFailure = true };
GivenList(failedListId, true, true, fetchResult2);
GivenList(failedListId, true, ImportListType.Automatic, fetchResult2);
var listResult = Subject.Fetch();
listResult.AnyFailure.Should().BeTrue();
@@ -176,10 +176,10 @@ namespace NzbDrone.Core.Test.ImportListTests
{
var passedListId = 1;
var fetchResult1 = new ImportListFetchResult { Movies = _listMovies, AnyFailure = false };
GivenList(passedListId, true, true, fetchResult1);
GivenList(passedListId, true, ImportListType.Automatic, fetchResult1);
var failedListId = 2;
var fetchResult2 = new ImportListFetchResult { Movies = _listMovies, AnyFailure = false };
GivenList(failedListId, true, true, fetchResult2);
GivenList(failedListId, true, ImportListType.Automatic, fetchResult2);
var listResult = Subject.Fetch();
listResult.AnyFailure.Should().BeFalse();

View File

@@ -104,7 +104,7 @@ namespace NzbDrone.Core.Test.ImportList
.Returns(cleanLevel);
}
private void GivenList(int id, bool enabledAuto)
private void GivenList(int id, ImportListType enabledAuto)
{
var importListDefinition = new ImportListDefinition { Id = id, EnableAuto = enabledAuto };
@@ -115,7 +115,7 @@ namespace NzbDrone.Core.Test.ImportList
CreateListResult(id, enabledAuto);
}
private Mock<IImportList> CreateListResult(int id, bool enabledAuto)
private Mock<IImportList> CreateListResult(int id, ImportListType enabledAuto)
{
var importListDefinition = new ImportListDefinition { Id = id, EnableAuto = enabledAuto };
@@ -133,7 +133,7 @@ namespace NzbDrone.Core.Test.ImportList
public void should_not_clean_library_if_config_value_disable()
{
_importListFetch.Movies.ForEach(m => m.ListId = 1);
GivenList(1, true);
GivenList(1, ImportListType.Automatic);
GivenCleanLevel("disabled");
Subject.Execute(_commandAll);
@@ -149,7 +149,7 @@ namespace NzbDrone.Core.Test.ImportList
public void should_log_only_on_clean_library_if_config_value_logonly()
{
_importListFetch.Movies.ForEach(m => m.ListId = 1);
GivenList(1, true);
GivenList(1, ImportListType.Automatic);
GivenCleanLevel("logOnly");
Mocker.GetMock<IMovieService>()
@@ -172,7 +172,7 @@ namespace NzbDrone.Core.Test.ImportList
public void should_unmonitor_on_clean_library_if_config_value_keepAndUnmonitor()
{
_importListFetch.Movies.ForEach(m => m.ListId = 1);
GivenList(1, true);
GivenList(1, ImportListType.Automatic);
GivenCleanLevel("keepAndUnmonitor");
Mocker.GetMock<IMovieService>()
@@ -197,7 +197,7 @@ namespace NzbDrone.Core.Test.ImportList
_importListFetch.Movies.ForEach(m => m.ListId = 1);
_importListFetch.Movies[0].TmdbId = 6;
GivenList(1, true);
GivenList(1, ImportListType.Automatic);
GivenCleanLevel("keepAndUnmonitor");
Mocker.GetMock<IMovieService>()
@@ -217,7 +217,7 @@ namespace NzbDrone.Core.Test.ImportList
_importListFetch.Movies[0].TmdbId = 0;
_importListFetch.Movies[0].ImdbId = "6";
GivenList(1, true);
GivenList(1, ImportListType.Automatic);
GivenCleanLevel("keepAndUnmonitor");
Mocker.GetMock<IMovieService>()
@@ -234,7 +234,7 @@ namespace NzbDrone.Core.Test.ImportList
public void should_delete_movies_not_files_on_clean_library_if_config_value_logonly()
{
_importListFetch.Movies.ForEach(m => m.ListId = 1);
GivenList(1, true);
GivenList(1, ImportListType.Automatic);
GivenCleanLevel("removeAndKeep");
Mocker.GetMock<IMovieService>()
@@ -260,7 +260,7 @@ namespace NzbDrone.Core.Test.ImportList
public void should_delete_movies_and_files_on_clean_library_if_config_value_logonly()
{
_importListFetch.Movies.ForEach(m => m.ListId = 1);
GivenList(1, true);
GivenList(1, ImportListType.Automatic);
GivenCleanLevel("removeAndDelete");
Mocker.GetMock<IMovieService>()
@@ -288,7 +288,7 @@ namespace NzbDrone.Core.Test.ImportList
_importListFetch.Movies.ForEach(m => m.ListId = 1);
GivenListFailure();
GivenList(1, true);
GivenList(1, ImportListType.Automatic);
GivenCleanLevel("disabled");
Subject.Execute(_commandAll);
@@ -301,7 +301,7 @@ namespace NzbDrone.Core.Test.ImportList
public void should_add_new_movies_from_single_list_to_library()
{
_importListFetch.Movies.ForEach(m => m.ListId = 1);
GivenList(1, true);
GivenList(1, ImportListType.Automatic);
GivenCleanLevel("disabled");
Subject.Execute(_commandAll);
@@ -317,8 +317,8 @@ namespace NzbDrone.Core.Test.ImportList
_importListFetch.Movies.ForEach(m => m.ListId = 1);
_importListFetch.Movies.AddRange(_list2Movies);
GivenList(1, true);
GivenList(2, true);
GivenList(1, ImportListType.Automatic);
GivenList(2, ImportListType.Automatic);
GivenCleanLevel("disabled");
@@ -335,8 +335,8 @@ namespace NzbDrone.Core.Test.ImportList
_importListFetch.Movies.ForEach(m => m.ListId = 1);
_importListFetch.Movies.AddRange(_list2Movies);
GivenList(1, true);
GivenList(2, false);
GivenList(1, ImportListType.Automatic);
GivenList(2, ImportListType.Manual);
GivenCleanLevel("disabled");
@@ -354,8 +354,8 @@ namespace NzbDrone.Core.Test.ImportList
_importListFetch.Movies.AddRange(_list2Movies);
_importListFetch.Movies[0].TmdbId = 4;
GivenList(1, true);
GivenList(2, true);
GivenList(1, ImportListType.Automatic);
GivenList(2, ImportListType.Automatic);
GivenCleanLevel("disabled");
@@ -372,8 +372,8 @@ namespace NzbDrone.Core.Test.ImportList
_importListFetch.Movies.ForEach(m => m.ListId = 1);
_importListFetch.Movies.AddRange(_list2Movies);
GivenList(1, true);
GivenList(2, true);
GivenList(1, ImportListType.Automatic);
GivenList(2, ImportListType.Automatic);
GivenCleanLevel("disabled");
@@ -394,8 +394,8 @@ namespace NzbDrone.Core.Test.ImportList
_importListFetch.Movies.ForEach(m => m.ListId = 1);
_importListFetch.Movies.AddRange(_list2Movies);
GivenList(1, true);
GivenList(2, true);
GivenList(1, ImportListType.Automatic);
GivenList(2, ImportListType.Automatic);
GivenCleanLevel("disabled");

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
@@ -40,15 +41,16 @@ namespace NzbDrone.Core.Test.MediaCoverTests
new MediaCover.MediaCover { CoverType = MediaCoverTypes.Banner }
};
Mocker.GetMock<IDiskProvider>().Setup(c => c.FileGetLastWrite(It.IsAny<string>()))
.Returns(new DateTime(1234));
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Media", "H264_sample.mp4");
var fileInfo = new FileInfo(path);
Mocker.GetMock<IDiskProvider>().Setup(c => c.FileExists(It.IsAny<string>()))
.Returns(true);
Mocker.GetMock<IDiskProvider>()
.Setup(c => c.GetFileInfo(It.IsAny<string>()))
.Returns(fileInfo);
Subject.ConvertToLocalUrls(12, covers);
covers.Single().Url.Should().Be("/MediaCover/12/banner.jpg?lastWrite=1234");
covers.Single().Url.Should().Be($"/MediaCover/12/banner.jpg?lastWrite={fileInfo.LastWriteTimeUtc.Ticks}");
}
[Test]
@@ -59,6 +61,13 @@ namespace NzbDrone.Core.Test.MediaCoverTests
new MediaCover.MediaCover { CoverType = MediaCoverTypes.Banner }
};
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Media", "NonExistant.mp4");
var fileInfo = new FileInfo(path);
Mocker.GetMock<IDiskProvider>()
.Setup(c => c.GetFileInfo(It.IsAny<string>()))
.Returns(fileInfo);
Subject.ConvertToLocalUrls(12, covers);
covers.Single().Url.Should().Be("/MediaCover/12/banner.jpg");

View File

@@ -364,7 +364,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
{
_namingConfig.StandardMovieFormat = "{Movie.Title}.{MEDIAINFO.FULL}";
_movieFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
_movieFile.MediaInfo = new MediaInfoModel()
{
VideoFormat = "AVC",
AudioFormat = "DTS",
@@ -376,12 +376,33 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
.Should().Be("South.Park.H264.DTS[EN+ES].[EN+ES+IT]");
}
[TestCase("Norwegian Bokmal", "NB")]
[TestCase("Swedis", "SV")]
[TestCase("Chinese", "ZH")]
public void should_format_languagecodes_properly(string language, string code)
{
_namingConfig.StandardMovieFormat = "{Movie.Title}.{MEDIAINFO.FULL}";
_movieFile.MediaInfo = new MediaInfoModel()
{
VideoCodec = "AVC",
AudioFormat = "DTS",
AudioChannels = 6,
AudioLanguages = "English",
Subtitles = language,
SchemaRevision = 3
};
Subject.BuildFileName(_movie, _movieFile)
.Should().Be($"South.Park.X264.DTS.[{code}]");
}
[Test]
public void should_exclude_english_in_mediainfo_audio_language()
{
_namingConfig.StandardMovieFormat = "{Movie.Title}.{MEDIAINFO.FULL}";
_movieFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
_movieFile.MediaInfo = new MediaInfoModel()
{
VideoFormat = "AVC",
AudioFormat = "DTS",
@@ -398,7 +419,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
{
_namingConfig.StandardMovieFormat = "{Movie.Title}.{MEDIAINFO.3D}.{MediaInfo.Simple}";
_movieFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
_movieFile.MediaInfo = new MediaInfoModel()
{
VideoFormat = "AVC",
VideoMultiViewCount = 2,

View File

@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
Subject.GetMovie(title);
Mocker.GetMock<IMovieService>()
.Verify(s => s.FindByTitle(Parser.Parser.ParseMovieTitle(title, false).MovieTitle), Times.Once());
.Verify(s => s.FindByTitle(Parser.Parser.ParseMovieTitle(title, false).MovieTitle, It.IsAny<int>(), null, null, null), Times.Once());
}
/*[Test]

View File

@@ -118,7 +118,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
Subject.Map(_parsedMovieInfo, "", null);
Mocker.GetMock<IMovieService>()
.Verify(v => v.FindByTitle(It.IsAny<string>(), It.IsAny<int>()), Times.Once());
.Verify(v => v.FindByTitle(It.IsAny<string>(), It.IsAny<int>(), null, null, null), Times.Once());
}
[Test]

View File

@@ -21,6 +21,7 @@ namespace NzbDrone.Core.Annotations
public Type SelectOptions { get; set; }
public string Section { get; set; }
public HiddenType Hidden { get; set; }
public PrivacyLevel Privacy { get; set; }
public string RequestAction { get; set; }
}
@@ -62,4 +63,12 @@ namespace NzbDrone.Core.Annotations
Hidden,
HiddenIfNotSet
}
public enum PrivacyLevel
{
Normal,
Password,
ApiKey,
UserName
}
}

View File

@@ -18,6 +18,7 @@ namespace NzbDrone.Core.Blacklisting
PagingSpec<Blacklist> Paged(PagingSpec<Blacklist> pagingSpec);
List<Blacklist> GetByMovieId(int movieId);
void Delete(int id);
void Delete(List<int> ids);
}
public class BlacklistService : IBlacklistService,
@@ -76,6 +77,11 @@ namespace NzbDrone.Core.Blacklisting
_blacklistRepository.Delete(id);
}
public void Delete(List<int> ids)
{
_blacklistRepository.DeleteMany(ids);
}
private bool SameNzb(Blacklist item, ReleaseInfo release)
{
if (item.PublishedDate == release.PublishDate)

View File

@@ -0,0 +1,15 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(185)]
public class add_alternative_title_indices : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Create.Index().OnTable("AlternativeTitles").OnColumn("CleanTitle");
Create.Index().OnTable("MovieTranslations").OnColumn("CleanTitle");
}
}
}

View File

@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
[FieldDefinition(2, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the deluge json url, see http://[host]:[port]/[urlBase]/json")]
public string UrlBase { get; set; }
[FieldDefinition(3, Label = "Password", Type = FieldType.Password)]
[FieldDefinition(3, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional")]

View File

@@ -36,10 +36,10 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Username", Type = FieldType.Textbox)]
[FieldDefinition(2, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(3, Label = "Password", Type = FieldType.Password)]
[FieldDefinition(3, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional. Creates a [category] subdirectory in the output directory.")]

View File

@@ -1,4 +1,4 @@
using FluentValidation;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
@@ -42,10 +42,10 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
[FieldDefinition(2, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the Hadouken url, e.g. http://[host]:[port]/[urlBase]/api")]
public string UrlBase { get; set; }
[FieldDefinition(3, Label = "Username", Type = FieldType.Textbox)]
[FieldDefinition(3, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(4, Label = "Password", Type = FieldType.Password)]
[FieldDefinition(4, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox)]

View File

@@ -45,7 +45,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
[FieldDefinition(2, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the NZBVortex url, e.g. http://[host]:[port]/[urlBase]/api")]
public string UrlBase { get; set; }
[FieldDefinition(3, Label = "API Key", Type = FieldType.Textbox)]
[FieldDefinition(3, Label = "API Key", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
[FieldDefinition(4, Label = "Group", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional")]

View File

@@ -45,10 +45,10 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
[FieldDefinition(2, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the nzbget url, e.g. http://[host]:[port]/[urlBase]/jsonrpc")]
public string UrlBase { get; set; }
[FieldDefinition(3, Label = "Username", Type = FieldType.Textbox)]
[FieldDefinition(3, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(4, Label = "Password", Type = FieldType.Password)]
[FieldDefinition(4, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional")]

View File

@@ -39,10 +39,10 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
[FieldDefinition(2, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the qBittorrent url, e.g. http://[host]:[port]/[urlBase]/api")]
public string UrlBase { get; set; }
[FieldDefinition(3, Label = "Username", Type = FieldType.Textbox)]
[FieldDefinition(3, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(4, Label = "Password", Type = FieldType.Password)]
[FieldDefinition(4, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional")]

View File

@@ -54,13 +54,13 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
[FieldDefinition(2, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the Sabnzbd url, e.g. http://[host]:[port]/[urlBase]/api")]
public string UrlBase { get; set; }
[FieldDefinition(3, Label = "API Key", Type = FieldType.Textbox)]
[FieldDefinition(3, Label = "API Key", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox)]
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(5, Label = "Password", Type = FieldType.Password)]
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional")]

View File

@@ -44,10 +44,10 @@ namespace NzbDrone.Core.Download.Clients.Transmission
[FieldDefinition(2, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the transmission rpc url, eg http://[host]:[port]/[urlBase]/rpc, defaults to '/transmission/'")]
public string UrlBase { get; set; }
[FieldDefinition(3, Label = "Username", Type = FieldType.Textbox)]
[FieldDefinition(3, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(4, Label = "Password", Type = FieldType.Password)]
[FieldDefinition(4, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional. Creates a [category] subdirectory in the output directory.")]

View File

@@ -43,10 +43,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
[FieldDefinition(3, Label = "Use SSL", Type = FieldType.Checkbox)]
public bool UseSsl { get; set; }
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox)]
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(5, Label = "Password", Type = FieldType.Password)]
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional.")]

View File

@@ -37,10 +37,10 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
[FieldDefinition(2, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the uTorrent url, e.g. http://[host]:[port]/[urlBase]/api")]
public string UrlBase { get; set; }
[FieldDefinition(3, Label = "Username", Type = FieldType.Textbox)]
[FieldDefinition(3, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(4, Label = "Password", Type = FieldType.Password)]
[FieldDefinition(4, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional")]

View File

@@ -65,7 +65,7 @@ namespace NzbDrone.Core.Extras
var sourcePath = localMovie.Path;
var sourceFolder = _diskProvider.GetParentFolder(sourcePath);
var sourceFileName = Path.GetFileNameWithoutExtension(sourcePath);
var files = _diskProvider.GetFiles(sourceFolder, SearchOption.TopDirectoryOnly).Where(f => f != localMovie.Path);
var files = _diskProvider.GetFiles(sourceFolder, SearchOption.AllDirectories).Where(f => f != localMovie.Path);
var wantedExtensions = _configService.ExtraFileExtensions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(e => e.Trim(' ', '.'))

View File

@@ -10,11 +10,13 @@ using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Extras.Metadata.Files;
using NzbDrone.Core.Languages;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Movies.Credits;
using NzbDrone.Core.Movies.Translations;
namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
{
@@ -25,11 +27,13 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
private readonly IDetectXbmcNfo _detectNfo;
private readonly IDiskProvider _diskProvider;
private readonly ICreditService _creditService;
private readonly IMovieTranslationService _movieTranslationsService;
public XbmcMetadata(IDetectXbmcNfo detectNfo,
IDiskProvider diskProvider,
IMapCoversToLocal mediaCoverService,
ICreditService creditService,
IMovieTranslationService movieTranslationsService,
Logger logger)
{
_logger = logger;
@@ -37,6 +41,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
_diskProvider = diskProvider;
_detectNfo = detectNfo;
_creditService = creditService;
_movieTranslationsService = movieTranslationsService;
}
private static readonly Regex MovieImagesRegex = new Regex(@"^(?<type>poster|banner|fanart|clearart|discart|keyart|landscape|logo|backdrop|clearlogo)\.(?:png|jpe?g)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
@@ -112,6 +117,15 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
if (Settings.MovieMetadata)
{
_logger.Debug("Generating Movie Metadata for: {0}", Path.Combine(movie.Path, movieFile.RelativePath));
var movieMetadataLanguage = (Settings.MovieMetadataLanguage == (int)Language.Original) ?
(int)movie.OriginalLanguage :
Settings.MovieMetadataLanguage;
var movieTranslations = _movieTranslationsService.GetAllTranslationsForMovie(movie.Id);
var selectedSettingsLanguage = Language.FindById(movieMetadataLanguage);
var movieTranslation = movieTranslations.FirstOrDefault(mt => mt.Language == selectedSettingsLanguage);
var watched = GetExistingWatchedStatus(movie, movieFile.RelativePath);
var sb = new StringBuilder();
@@ -128,14 +142,14 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
var details = new XElement("movie");
details.Add(new XElement("title", movie.Title));
details.Add(new XElement("title", movieTranslation?.Title ?? movie.Title));
if (movie.Ratings != null && movie.Ratings.Votes > 0)
{
details.Add(new XElement("rating", movie.Ratings.Value));
}
details.Add(new XElement("plot", movie.Overview));
details.Add(new XElement("plot", movieTranslation?.Overview ?? movie.Overview));
details.Add(new XElement("id", movie.ImdbId));
details.Add(new XElement("tmdbid", movie.TmdbId));

View File

@@ -1,5 +1,6 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Languages;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
@@ -20,6 +21,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
{
MovieMetadata = true;
MovieMetadataURL = false;
MovieMetadataLanguage = (int)Language.English;
MovieImages = true;
UseMovieNfo = false;
}
@@ -30,10 +32,13 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
[FieldDefinition(1, Label = "Movie Metadata URL", Type = FieldType.Checkbox, HelpText = "Radarr will write the tmdb/imdb url in the .nfo file", Advanced = true)]
public bool MovieMetadataURL { get; set; }
[FieldDefinition(2, Label = "Movie Images", Type = FieldType.Checkbox)]
[FieldDefinition(2, Label = "Metadata Language", Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), HelpText = "Radarr will write metadata in the selected language if available")]
public int MovieMetadataLanguage { get; set; }
[FieldDefinition(3, Label = "Movie Images", Type = FieldType.Checkbox)]
public bool MovieImages { get; set; }
[FieldDefinition(3, Label = "Use Movie.nfo", Type = FieldType.Checkbox, HelpText = "Radarr will write metadata to movie.nfo instead of the default <movie-filename>.nfo")]
[FieldDefinition(4, Label = "Use Movie.nfo", Type = FieldType.Checkbox, HelpText = "Radarr will write metadata to movie.nfo instead of the default <movie-filename>.nfo")]
public bool UseMovieNfo { get; set; }
public bool IsValid => true;

View File

@@ -0,0 +1,59 @@
using System;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Localization;
using NzbDrone.Core.ThingiProvider.Events;
namespace NzbDrone.Core.HealthCheck.Checks
{
[CheckOn(typeof(ProviderUpdatedEvent<IIndexer>))]
[CheckOn(typeof(ProviderDeletedEvent<IIndexer>))]
[CheckOn(typeof(ProviderStatusChangedEvent<IIndexer>))]
public class IndexerLongTermStatusCheck : HealthCheckBase
{
private readonly IIndexerFactory _providerFactory;
private readonly IIndexerStatusService _providerStatusService;
public IndexerLongTermStatusCheck(IIndexerFactory providerFactory,
IIndexerStatusService providerStatusService,
ILocalizationService localizationService)
: base(localizationService)
{
_providerFactory = providerFactory;
_providerStatusService = providerStatusService;
}
public override HealthCheck Check()
{
var enabledProviders = _providerFactory.GetAvailableProviders();
var backOffProviders = enabledProviders.Join(_providerStatusService.GetBlockedProviders(),
i => i.Definition.Id,
s => s.ProviderId,
(i, s) => new { Provider = i, Status = s })
.Where(p => p.Status.InitialFailure.HasValue &&
p.Status.InitialFailure.Value.Before(
DateTime.UtcNow.AddHours(-6)))
.ToList();
if (backOffProviders.Empty())
{
return new HealthCheck(GetType());
}
if (backOffProviders.Count == enabledProviders.Count)
{
return new HealthCheck(GetType(),
HealthCheckResult.Error,
_localizationService.GetLocalizedString("IndexerLongTermStatusCheckAllClientMessage"),
"#indexers-are-unavailable-due-to-failures");
}
return new HealthCheck(GetType(),
HealthCheckResult.Warning,
string.Format(_localizationService.GetLocalizedString("IndexerLongTermStatusCheckSingleClientMessage"),
string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))),
"#indexers-are-unavailable-due-to-failures");
}
}
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Indexers;
@@ -25,10 +26,13 @@ namespace NzbDrone.Core.HealthCheck.Checks
{
var enabledProviders = _providerFactory.GetAvailableProviders();
var backOffProviders = enabledProviders.Join(_providerStatusService.GetBlockedProviders(),
i => i.Definition.Id,
s => s.ProviderId,
(i, s) => new { Provider = i, Status = s })
.ToList();
i => i.Definition.Id,
s => s.ProviderId,
(i, s) => new { Provider = i, Status = s })
.Where(p => p.Status.InitialFailure.HasValue &&
p.Status.InitialFailure.Value.After(
DateTime.UtcNow.AddHours(-6)))
.ToList();
if (backOffProviders.Empty())
{
@@ -37,10 +41,17 @@ namespace NzbDrone.Core.HealthCheck.Checks
if (backOffProviders.Count == enabledProviders.Count)
{
return new HealthCheck(GetType(), HealthCheckResult.Error, _localizationService.GetLocalizedString("IndexerStatusCheckAllClientMessage"), "#indexers-are-unavailable-due-to-failures");
return new HealthCheck(GetType(),
HealthCheckResult.Error,
_localizationService.GetLocalizedString("IndexerStatusCheckAllClientMessage"),
"#indexers-are-unavailable-due-to-failures");
}
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("IndexerStatusCheckSingleClientMessage"), string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))), "#indexers-are-unavailable-due-to-failures");
return new HealthCheck(GetType(),
HealthCheckResult.Warning,
string.Format(_localizationService.GetLocalizedString("IndexerStatusCheckSingleClientMessage"),
string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))),
"#indexers-are-unavailable-due-to-failures");
}
}
}

View File

@@ -9,9 +9,9 @@ namespace NzbDrone.Core.ImportLists.CouchPotato
{
public override string Name => "CouchPotato";
public override ImportListType ListType => ImportListType.Program;
public override ImportListSource ListType => ImportListSource.Program;
public override bool Enabled => true;
public override bool EnableAuto => false;
public override ImportListType EnableAuto => ImportListType.Manual;
public CouchPotatoImport(IHttpClient httpClient, IImportListStatusService importListStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, importListStatusService, configService, parsingService, logger)

View File

@@ -36,7 +36,7 @@ namespace NzbDrone.Core.ImportLists.CouchPotato
[FieldDefinition(2, Label = "CouchPotato Url Base", HelpText = "If you have CouchPotato configured via reverse proxy put the base path here. e.g. couchpotato. Leave blank for no base URL.")]
public string UrlBase { get; set; }
[FieldDefinition(3, Label = "CouchPotato API Key", HelpText = "CouchPotato API Key. This can found within Settings > General")]
[FieldDefinition(3, Label = "CouchPotato API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "CouchPotato API Key. This can found within Settings > General")]
public string ApiKey { get; set; }
[FieldDefinition(4, Label = "Only Wanted", HelpText = "Only add wanted movies.", Type = FieldType.Checkbox)]

View File

@@ -5,9 +5,9 @@ namespace NzbDrone.Core.ImportLists
public interface IImportList : IProvider
{
bool Enabled { get; }
bool EnableAuto { get; }
ImportListType EnableAuto { get; }
ImportListType ListType { get; }
ImportListSource ListType { get; }
ImportListFetchResult Fetch();
}
}

View File

@@ -31,9 +31,9 @@ namespace NzbDrone.Core.ImportLists
public abstract string Name { get; }
public abstract ImportListType ListType { get; }
public abstract ImportListSource ListType { get; }
public abstract bool Enabled { get; }
public abstract bool EnableAuto { get; }
public abstract ImportListType EnableAuto { get; }
public abstract ImportListFetchResult Fetch();
@@ -59,7 +59,7 @@ namespace NzbDrone.Core.ImportLists
{
Name = GetType().Name,
Enabled = config.Validate().IsValid && Enabled,
EnableAuto = true,
EnableAuto = ImportListType.Automatic,
Implementation = GetType().Name,
Settings = config
};

View File

@@ -12,7 +12,7 @@ namespace NzbDrone.Core.ImportLists
}
public bool Enabled { get; set; }
public bool EnableAuto { get; set; }
public ImportListType EnableAuto { get; set; }
public bool ShouldMonitor { get; set; }
public MovieStatusType MinimumAvailability { get; set; }
public int ProfileId { get; set; }
@@ -20,6 +20,6 @@ namespace NzbDrone.Core.ImportLists
public bool SearchOnAdd { get; set; }
public override bool Enable => Enabled;
public ImportListType ListType { get; set; }
public ImportListSource ListType { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
namespace NzbDrone.Core.ImportLists
{
public enum ImportListSource
{
Program,
TMDB,
Trakt,
Other,
Advanced
}
}

View File

@@ -51,7 +51,7 @@ namespace NzbDrone.Core.ImportLists
{
var result = _listFetcherAndParser.Fetch();
if (_importListFactory.Enabled().Where(a => ((ImportListDefinition)a.Definition).EnableAuto).Empty())
if (_importListFactory.Enabled().Where(a => ((ImportListDefinition)a.Definition).EnableAuto == ImportListType.Automatic).Empty())
{
_logger.Info("No auto enabled lists, skipping sync and cleaning");
return;
@@ -67,7 +67,7 @@ namespace NzbDrone.Core.ImportLists
private void ProcessMovieReport(ImportListDefinition importList, ImportListMovie report, List<ImportExclusion> listExclusions, List<int> dbMovies, List<Movie> moviesToAdd)
{
if (report.TmdbId == 0 || !importList.EnableAuto)
if (report.TmdbId == 0 || importList.EnableAuto != ImportListType.Automatic)
{
return;
}

View File

@@ -2,10 +2,8 @@ namespace NzbDrone.Core.ImportLists
{
public enum ImportListType
{
Program,
TMDB,
Trakt,
Other,
Advanced
Manual,
Automatic,
Exclusion
}
}

View File

@@ -11,9 +11,9 @@ namespace NzbDrone.Core.ImportLists.RSSImport
{
public override string Name => "RSS List";
public override ImportListType ListType => ImportListType.Advanced;
public override ImportListSource ListType => ImportListSource.Advanced;
public override bool Enabled => true;
public override bool EnableAuto => false;
public override ImportListType EnableAuto => ImportListType.Manual;
public RSSImport(IHttpClient httpClient, IImportListStatusService importListStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, importListStatusService, configService, parsingService, logger)
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.ImportLists.RSSImport
{
Name = "IMDb List",
Enabled = Enabled,
EnableAuto = true,
EnableAuto = ImportListType.Automatic,
ProfileId = 1,
Implementation = GetType().Name,
Settings = new RSSImportSettings { Link = "https://rss.imdb.com/list/YOURLISTID" },
@@ -42,7 +42,7 @@ namespace NzbDrone.Core.ImportLists.RSSImport
{
Name = "IMDb Watchlist",
Enabled = Enabled,
EnableAuto = true,
EnableAuto = ImportListType.Automatic,
ProfileId = 1,
Implementation = GetType().Name,
Settings = new RSSImportSettings { Link = "https://rss.imdb.com/user/IMDBUSERID/watchlist" },

View File

@@ -16,9 +16,9 @@ namespace NzbDrone.Core.ImportLists.Radarr
private readonly IRadarrV3Proxy _radarrV3Proxy;
public override string Name => "Radarr";
public override bool Enabled => true;
public override bool EnableAuto => false;
public override ImportListType EnableAuto => ImportListType.Manual;
public override ImportListType ListType => ImportListType.Program;
public override ImportListSource ListType => ImportListSource.Program;
public RadarrImport(IRadarrV3Proxy radarrV3Proxy,
IImportListStatusService importListStatusService,

View File

@@ -31,7 +31,7 @@ namespace NzbDrone.Core.ImportLists.Radarr
[FieldDefinition(0, Label = "Full URL", HelpText = "URL, including port, of the Radarr V3 instance to import from")]
public string BaseUrl { get; set; }
[FieldDefinition(1, Label = "API Key", HelpText = "Apikey of the Radarr V3 instance to import from")]
[FieldDefinition(1, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "Apikey of the Radarr V3 instance to import from")]
public string ApiKey { get; set; }
[FieldDefinition(2, Type = FieldType.Device, RequestAction = "getProfiles", Label = "Profiles", HelpText = "Profiles from the source instance to import from")]

View File

@@ -11,9 +11,9 @@ namespace NzbDrone.Core.ImportLists.RadarrList
{
public override string Name => "Custom Lists";
public override ImportListType ListType => ImportListType.Advanced;
public override ImportListSource ListType => ImportListSource.Advanced;
public override bool Enabled => true;
public override bool EnableAuto => false;
public override ImportListType EnableAuto => ImportListType.Manual;
public RadarrListImport(IHttpClient httpClient,
IImportListStatusService importListStatusService,

View File

@@ -14,9 +14,9 @@ namespace NzbDrone.Core.ImportLists.RadarrList2.IMDbList
public override string Name => "IMDb Lists";
public override ImportListType ListType => ImportListType.Other;
public override ImportListSource ListType => ImportListSource.Other;
public override bool Enabled => true;
public override bool EnableAuto => false;
public override ImportListType EnableAuto => ImportListType.Manual;
public IMDbListImport(IRadarrCloudRequestBuilder requestBuilder,
IHttpClient httpClient,
@@ -42,7 +42,7 @@ namespace NzbDrone.Core.ImportLists.RadarrList2.IMDbList
{
Name = "IMDb Top 250",
Enabled = Enabled,
EnableAuto = true,
EnableAuto = ImportListType.Automatic,
ProfileId = 1,
Implementation = GetType().Name,
Settings = new IMDbListSettings { ListId = "top250" },
@@ -51,7 +51,7 @@ namespace NzbDrone.Core.ImportLists.RadarrList2.IMDbList
{
Name = "IMDb Popular Movies",
Enabled = Enabled,
EnableAuto = true,
EnableAuto = ImportListType.Automatic,
ProfileId = 1,
Implementation = GetType().Name,
Settings = new IMDbListSettings { ListId = "popular" },

View File

@@ -32,9 +32,9 @@ namespace NzbDrone.Core.ImportLists.RadarrList2.IMDbList
if (_settings.ListId.StartsWith("ls", StringComparison.OrdinalIgnoreCase))
{
//Parse TSV response from IMDB export
var row = importResponse.Content.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
var rows = importResponse.Content.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
movies = row.Skip(1).SelectList(m => new ImportListMovie { ImdbId = m.Split(',')[1] });
movies = rows.Skip(1).SelectList(m => m.Split(',')).Where(m => m.Length > 1).SelectList(i => new ImportListMovie { ImdbId = i[1] });
return movies;
}

View File

@@ -12,9 +12,9 @@ namespace NzbDrone.Core.ImportLists.RadarrList2.StevenLu
public override string Name => "StevenLu List";
public override ImportListType ListType => ImportListType.Other;
public override ImportListSource ListType => ImportListSource.Other;
public override bool Enabled => true;
public override bool EnableAuto => false;
public override ImportListType EnableAuto => ImportListType.Manual;
public StevenLu2Import(IRadarrCloudRequestBuilder requestBuilder,
IHttpClient httpClient,

View File

@@ -9,9 +9,9 @@ namespace NzbDrone.Core.ImportLists.StevenLu
{
public override string Name => "StevenLu Custom";
public override ImportListType ListType => ImportListType.Advanced;
public override ImportListSource ListType => ImportListSource.Advanced;
public override bool Enabled => true;
public override bool EnableAuto => false;
public override ImportListType EnableAuto => ImportListType.Manual;
public StevenLuImport(IHttpClient httpClient,
IImportListStatusService importListStatusService,

View File

@@ -22,7 +22,7 @@ namespace NzbDrone.Core.ImportLists.TMDb.Collection
public override string Name => "TMDb Collection";
public override bool Enabled => true;
public override bool EnableAuto => false;
public override ImportListType EnableAuto => ImportListType.Manual;
public override IParseImportListResponse GetParser()
{

View File

@@ -22,7 +22,7 @@ namespace NzbDrone.Core.ImportLists.TMDb.List
public override string Name => "TMDb List";
public override bool Enabled => true;
public override bool EnableAuto => false;
public override ImportListType EnableAuto => ImportListType.Manual;
public override IParseImportListResponse GetParser()
{

View File

@@ -22,7 +22,7 @@ namespace NzbDrone.Core.ImportLists.TMDb.Person
public override string Name => "TMDb Person";
public override bool Enabled => true;
public override bool EnableAuto => false;
public override ImportListType EnableAuto => ImportListType.Manual;
public override IParseImportListResponse GetParser()
{

View File

@@ -22,7 +22,7 @@ namespace NzbDrone.Core.ImportLists.TMDb.Popular
public override string Name => "TMDb Popular";
public override bool Enabled => true;
public override bool EnableAuto => false;
public override ImportListType EnableAuto => ImportListType.Manual;
public override IParseImportListResponse GetParser()
{

View File

@@ -10,7 +10,7 @@ namespace NzbDrone.Core.ImportLists.TMDb
public abstract class TMDbImportListBase<TSettings> : HttpImportListBase<TSettings>
where TSettings : TMDbSettingsBase<TSettings>, new()
{
public override ImportListType ListType => ImportListType.TMDB;
public override ImportListSource ListType => ImportListSource.TMDB;
public readonly ISearchForNewMovie _skyhookProxy;
public readonly IHttpRequestBuilderFactory _requestBuilder;

View File

@@ -24,7 +24,7 @@ namespace NzbDrone.Core.ImportLists.TMDb.User
public override string Name => "TMDb User";
public override bool Enabled => true;
public override bool EnableAuto => false;
public override ImportListType EnableAuto => ImportListType.Manual;
public override IParseImportListResponse GetParser()
{

View File

@@ -21,7 +21,7 @@ namespace NzbDrone.Core.ImportLists.Trakt.List
public override string Name => "Trakt List";
public override bool Enabled => true;
public override bool EnableAuto => false;
public override ImportListType EnableAuto => ImportListType.Manual;
public override IImportListRequestGenerator GetRequestGenerator()
{

View File

@@ -19,7 +19,7 @@ namespace NzbDrone.Core.ImportLists.Trakt.List
{
}
[FieldDefinition(1, Label = "Username", HelpText = "Username for the List to import from")]
[FieldDefinition(1, Label = "Username", Privacy = PrivacyLevel.UserName, HelpText = "Username for the List to import from")]
public string Username { get; set; }
[FieldDefinition(2, Label = "List Name", HelpText = "List name for import, list must be public or you must have access to the list")]

View File

@@ -21,7 +21,7 @@ namespace NzbDrone.Core.ImportLists.Trakt.Popular
public override string Name => "Trakt Popular List";
public override bool Enabled => true;
public override bool EnableAuto => false;
public override ImportListType EnableAuto => ImportListType.Manual;
public override IParseImportListResponse GetParser()
{

View File

@@ -14,7 +14,7 @@ namespace NzbDrone.Core.ImportLists.Trakt
{
public ITraktProxy _traktProxy;
private readonly IImportListRepository _importListRepository;
public override ImportListType ListType => ImportListType.Trakt;
public override ImportListSource ListType => ImportListSource.Trakt;
protected TraktImportBase(IImportListRepository importListRepository,
ITraktProxy traktProxy,

View File

@@ -21,7 +21,7 @@ namespace NzbDrone.Core.ImportLists.Trakt.User
public override string Name => "Trakt User";
public override bool Enabled => true;
public override bool EnableAuto => false;
public override ImportListType EnableAuto => ImportListType.Manual;
public override IImportListRequestGenerator GetRequestGenerator()
{

View File

@@ -33,10 +33,10 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
[FieldDefinition(0, Label = "API URL", Advanced = true, HelpText = "Do not change this unless you know what you're doing. Since you Passkey will be sent to that host.")]
public string BaseUrl { get; set; }
[FieldDefinition(1, Type = FieldType.TagSelect, SelectOptions = typeof(LanguageFieldConverter), Label = "Multi Languages", HelpText = "What languages are normally in a multi release on this indexer?", Advanced = true)]
[FieldDefinition(1, Type = FieldType.Select, SelectOptions = typeof(LanguageFieldConverter), Label = "Multi Languages", HelpText = "What languages are normally in a multi release on this indexer?", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(2, Label = "Passkey")]
[FieldDefinition(2, Label = "Passkey", Privacy = PrivacyLevel.ApiKey)]
public string Passkey { get; set; }
[FieldDefinition(3, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]

View File

@@ -39,13 +39,13 @@ namespace NzbDrone.Core.Indexers.FileList
RequiredFlags = new List<int>();
}
[FieldDefinition(0, Label = "Username")]
[FieldDefinition(0, Label = "Username", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(1, Label = "Passkey")]
[FieldDefinition(1, Label = "Passkey", Privacy = PrivacyLevel.ApiKey)]
public string Passkey { get; set; }
[FieldDefinition(2, Type = FieldType.TagSelect, SelectOptions = typeof(LanguageFieldConverter), Label = "Multi Languages", HelpText = "What languages are normally in a multi release on this indexer?", Advanced = true)]
[FieldDefinition(2, Type = FieldType.Select, SelectOptions = typeof(LanguageFieldConverter), Label = "Multi Languages", HelpText = "What languages are normally in a multi release on this indexer?", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(3, Label = "API URL", Advanced = true, HelpText = "Do not change this unless you know what you're doing. Since your API key will be sent to that host.")]

View File

@@ -34,13 +34,13 @@ namespace NzbDrone.Core.Indexers.HDBits
RequiredFlags = new List<int>();
}
[FieldDefinition(0, Label = "Username")]
[FieldDefinition(0, Label = "Username", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(1, Type = FieldType.TagSelect, SelectOptions = typeof(LanguageFieldConverter), Label = "Multi Languages", HelpText = "What languages are normally in a multi release on this indexer?", Advanced = true)]
[FieldDefinition(1, Type = FieldType.Select, SelectOptions = typeof(LanguageFieldConverter), Label = "Multi Languages", HelpText = "What languages are normally in a multi release on this indexer?", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(2, Label = "API Key")]
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
[FieldDefinition(3, Label = "API URL", Advanced = true, HelpText = "Do not change this unless you know what you're doing. Since your API key will be sent to that host.")]

View File

@@ -40,7 +40,7 @@ namespace NzbDrone.Core.Indexers.IPTorrents
[FieldDefinition(0, Label = "Feed URL", HelpText = "The full RSS feed url generated by IPTorrents, using only the categories you selected (HD, SD, x264, etc ...)")]
public string BaseUrl { get; set; }
[FieldDefinition(1, Type = FieldType.TagSelect, SelectOptions = typeof(LanguageFieldConverter), Label = "Multi Languages", HelpText = "What languages are normally in a multi release on this indexer?", Advanced = true)]
[FieldDefinition(1, Type = FieldType.Select, SelectOptions = typeof(LanguageFieldConverter), Label = "Multi Languages", HelpText = "What languages are normally in a multi release on this indexer?", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(2, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]

View File

@@ -69,10 +69,10 @@ namespace NzbDrone.Core.Indexers.Newznab
[FieldDefinition(1, Label = "API Path", HelpText = "Path to the api, usually /api", Advanced = true)]
public string ApiPath { get; set; }
[FieldDefinition(1, Type = FieldType.TagSelect, SelectOptions = typeof(LanguageFieldConverter), Label = "Multi Languages", HelpText = "What languages are normally in a multi release on this indexer?", Advanced = true)]
[FieldDefinition(1, Type = FieldType.Select, SelectOptions = typeof(LanguageFieldConverter), Label = "Multi Languages", HelpText = "What languages are normally in a multi release on this indexer?", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(2, Label = "API Key")]
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
[FieldDefinition(3, Label = "Categories", HelpText = "Comma Separated list, leave blank to disable all categories", Advanced = true)]

View File

@@ -35,7 +35,7 @@ namespace NzbDrone.Core.Indexers.Nyaa
[FieldDefinition(0, Label = "Website URL")]
public string BaseUrl { get; set; }
[FieldDefinition(1, Type = FieldType.TagSelect, SelectOptions = typeof(LanguageFieldConverter), Label = "Multi Languages", HelpText = "What languages are normally in a multi release on this indexer?", Advanced = true)]
[FieldDefinition(1, Type = FieldType.Select, SelectOptions = typeof(LanguageFieldConverter), Label = "Multi Languages", HelpText = "What languages are normally in a multi release on this indexer?", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(2, Label = "Additional Parameters", Advanced = true, HelpText = "Please note if you change the category you will have to add required/restricted rules about the subgroups to avoid foreign language releases.")]

Some files were not shown because too many files have changed in this diff Show More