mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-14 15:46:43 -04:00
Compare commits
45 Commits
speed-up-l
...
new-exclus
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
793cf22c3b | ||
|
|
76cabb4927 | ||
|
|
5b83d09d5e | ||
|
|
82eadcffaa | ||
|
|
1ebb71db59 | ||
|
|
662b3894c2 | ||
|
|
a1c21af9b5 | ||
|
|
67fca87c44 | ||
|
|
6ee2780370 | ||
|
|
0086e2699e | ||
|
|
37c9701237 | ||
|
|
6d452d8479 | ||
|
|
e82f0c01d8 | ||
|
|
ee456c3291 | ||
|
|
9986f0119b | ||
|
|
5d4da26195 | ||
|
|
5ed448c930 | ||
|
|
9263e31b7b | ||
|
|
516122c6f3 | ||
|
|
256a50abab | ||
|
|
315929bc5e | ||
|
|
4a681601b2 | ||
|
|
ab3b5bdf8b | ||
|
|
e52288bd67 | ||
|
|
f2f26d88b9 | ||
|
|
27354507cb | ||
|
|
b7aa1df219 | ||
|
|
2d7942d69c | ||
|
|
0a8dd85856 | ||
|
|
f917d0e9bc | ||
|
|
024e4df99c | ||
|
|
49fa402c55 | ||
|
|
02c95658c4 | ||
|
|
3bc4231640 | ||
|
|
a9a0d47f9f | ||
|
|
0dd05b2dac | ||
|
|
3986433884 | ||
|
|
f637976530 | ||
|
|
603d26bb5f | ||
|
|
6b41ad7442 | ||
|
|
4c049ac3d9 | ||
|
|
84df3e8b5b | ||
|
|
0ff76e14bb | ||
|
|
5433c6364c | ||
|
|
038e3d5c44 |
@@ -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'))
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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 }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -549,7 +549,7 @@ class MovieDetails extends Component {
|
||||
>
|
||||
<span className={styles.sizeOnDisk}>
|
||||
{
|
||||
formatBytes(sizeOnDisk)
|
||||
formatBytes(sizeOnDisk || 0)
|
||||
}
|
||||
</span>
|
||||
</InfoLabel>
|
||||
|
||||
@@ -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();
|
||||
|
||||
37
frontend/src/MovieFile/Edit/FileEditModal.js
Normal file
37
frontend/src/MovieFile/Edit/FileEditModal.js
Normal 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;
|
||||
237
frontend/src/MovieFile/Edit/FileEditModalContent.js
Normal file
237
frontend/src/MovieFile/Edit/FileEditModalContent.js
Normal 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;
|
||||
139
frontend/src/MovieFile/Edit/FileEditModalContentConnector.js
Normal file
139
frontend/src/MovieFile/Edit/FileEditModalContentConnector.js
Normal 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);
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
|
||||
48
frontend/src/Store/Actions/Settings/indexerFlags.js
Normal file
48
frontend/src/Store/Actions/Settings/indexerFlags.js
Normal 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: {
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
@@ -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 })
|
||||
]));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
@@ -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 });
|
||||
}),
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -19,6 +19,7 @@ function getInternalLink(source) {
|
||||
case 'IndexerRssCheck':
|
||||
case 'IndexerSearchCheck':
|
||||
case 'IndexerStatusCheck':
|
||||
case 'IndexerLongTermStatusCheck':
|
||||
return (
|
||||
<IconButton
|
||||
name={icons.SETTINGS}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>()))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")]
|
||||
|
||||
@@ -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.")]
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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.")]
|
||||
|
||||
@@ -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.")]
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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(' ', '.'))
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
11
src/NzbDrone.Core/ImportLists/ImportListSource.cs
Normal file
11
src/NzbDrone.Core/ImportLists/ImportListSource.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace NzbDrone.Core.ImportLists
|
||||
{
|
||||
public enum ImportListSource
|
||||
{
|
||||
Program,
|
||||
TMDB,
|
||||
Trakt,
|
||||
Other,
|
||||
Advanced
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,8 @@ namespace NzbDrone.Core.ImportLists
|
||||
{
|
||||
public enum ImportListType
|
||||
{
|
||||
Program,
|
||||
TMDB,
|
||||
Trakt,
|
||||
Other,
|
||||
Advanced
|
||||
Manual,
|
||||
Automatic,
|
||||
Exclusion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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.")]
|
||||
|
||||
@@ -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.")]
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user