mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-03-05 13:20:20 -05:00
Compare commits
29 Commits
v4.0.4.161
...
v4.0.4.169
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70bc26dc19 | ||
|
|
a2e0002a08 | ||
|
|
d7ceb11a64 | ||
|
|
cc5b5463f2 | ||
|
|
9b4ff657af | ||
|
|
aea50fa47e | ||
|
|
05edd44ed6 | ||
|
|
4440aa3cac | ||
|
|
084fcc2295 | ||
|
|
536ff142c3 | ||
|
|
627b2a4289 | ||
|
|
9734c2d144 | ||
|
|
c7c1e3ac9e | ||
|
|
429444d085 | ||
|
|
5cb649e9d8 | ||
|
|
cac7d239ea | ||
|
|
3940059ea3 | ||
|
|
20d00fe88c | ||
|
|
b4d05214ae | ||
|
|
cc0a284660 | ||
|
|
f50a263f4f | ||
|
|
29176c8367 | ||
|
|
1eddf3a152 | ||
|
|
8360dd7a7b | ||
|
|
7e8d8500f2 | ||
|
|
cae134ec7b | ||
|
|
f81bb3ec19 | ||
|
|
128309068d | ||
|
|
73a4bdea52 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -48,7 +48,7 @@ jobs:
|
||||
|
||||
echo "SDK_PATH=${{ env.DOTNET_ROOT }}/sdk/${DOTNET_VERSION}" >> "$GITHUB_ENV"
|
||||
echo "SONARR_VERSION=$SONARR_VERSION" >> "$GITHUB_ENV"
|
||||
echo "BRANCH=${RAW_BRANCH_NAME/\//-}" >> "$GITHUB_ENV"
|
||||
echo "BRANCH=${RAW_BRANCH_NAME//\//-}" >> "$GITHUB_ENV"
|
||||
|
||||
echo "framework=${{ env.FRAMEWORK }}" >> "$GITHUB_OUTPUT"
|
||||
echo "major_version=${{ env.SONARR_MAJOR_VERSION }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
1
.github/workflows/labeler.yml
vendored
1
.github/workflows/labeler.yml
vendored
@@ -8,5 +8,6 @@ jobs:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'Sonarr/Sonarr'
|
||||
steps:
|
||||
- uses: actions/labeler@v5
|
||||
|
||||
1
.github/workflows/lock.yml
vendored
1
.github/workflows/lock.yml
vendored
@@ -8,6 +8,7 @@ on:
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'Sonarr/Sonarr'
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
|
||||
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
@@ -20,6 +21,7 @@ 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 BlocklistFilterModal from './BlocklistFilterModal';
|
||||
import BlocklistRowConnector from './BlocklistRowConnector';
|
||||
|
||||
class Blocklist extends Component {
|
||||
@@ -114,9 +116,13 @@ class Blocklist extends Component {
|
||||
error,
|
||||
items,
|
||||
columns,
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
customFilters,
|
||||
totalRecords,
|
||||
isRemoving,
|
||||
isClearingBlocklistExecuting,
|
||||
onFilterSelect,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
@@ -161,6 +167,15 @@ class Blocklist extends Component {
|
||||
iconName={icons.TABLE}
|
||||
/>
|
||||
</TableOptionsModalWrapper>
|
||||
|
||||
<FilterMenu
|
||||
alignMenu={align.RIGHT}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
filters={filters}
|
||||
customFilters={customFilters}
|
||||
filterModalConnectorComponent={BlocklistFilterModal}
|
||||
onFilterSelect={onFilterSelect}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
</PageToolbar>
|
||||
|
||||
@@ -180,7 +195,11 @@ class Blocklist extends Component {
|
||||
{
|
||||
isPopulated && !error && !items.length &&
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('NoHistoryBlocklist')}
|
||||
{
|
||||
selectedFilterKey === 'all' ?
|
||||
translate('NoHistoryBlocklist') :
|
||||
translate('BlocklistFilterHasNoItems')
|
||||
}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
@@ -251,11 +270,15 @@ Blocklist.propTypes = {
|
||||
error: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
totalRecords: PropTypes.number,
|
||||
isRemoving: PropTypes.bool.isRequired,
|
||||
isClearingBlocklistExecuting: PropTypes.bool.isRequired,
|
||||
onRemoveSelected: PropTypes.func.isRequired,
|
||||
onClearBlocklistPress: PropTypes.func.isRequired
|
||||
onClearBlocklistPress: PropTypes.func.isRequired,
|
||||
onFilterSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default Blocklist;
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as commandNames from 'Commands/commandNames';
|
||||
import withCurrentPage from 'Components/withCurrentPage';
|
||||
import * as blocklistActions from 'Store/Actions/blocklistActions';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||
import Blocklist from './Blocklist';
|
||||
@@ -13,10 +14,12 @@ import Blocklist from './Blocklist';
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.blocklist,
|
||||
createCustomFiltersSelector('blocklist'),
|
||||
createCommandExecutingSelector(commandNames.CLEAR_BLOCKLIST),
|
||||
(blocklist, isClearingBlocklistExecuting) => {
|
||||
(blocklist, customFilters, isClearingBlocklistExecuting) => {
|
||||
return {
|
||||
isClearingBlocklistExecuting,
|
||||
customFilters,
|
||||
...blocklist
|
||||
};
|
||||
}
|
||||
@@ -97,6 +100,10 @@ class BlocklistConnector extends Component {
|
||||
this.props.setBlocklistSort({ sortKey });
|
||||
};
|
||||
|
||||
onFilterSelect = (selectedFilterKey) => {
|
||||
this.props.setBlocklistFilter({ selectedFilterKey });
|
||||
};
|
||||
|
||||
onClearBlocklistPress = () => {
|
||||
this.props.executeCommand({ name: commandNames.CLEAR_BLOCKLIST });
|
||||
};
|
||||
@@ -122,6 +129,7 @@ class BlocklistConnector extends Component {
|
||||
onPageSelect={this.onPageSelect}
|
||||
onRemoveSelected={this.onRemoveSelected}
|
||||
onSortPress={this.onSortPress}
|
||||
onFilterSelect={this.onFilterSelect}
|
||||
onTableOptionChange={this.onTableOptionChange}
|
||||
onClearBlocklistPress={this.onClearBlocklistPress}
|
||||
{...this.props}
|
||||
@@ -142,6 +150,7 @@ BlocklistConnector.propTypes = {
|
||||
gotoBlocklistPage: PropTypes.func.isRequired,
|
||||
removeBlocklistItems: PropTypes.func.isRequired,
|
||||
setBlocklistSort: PropTypes.func.isRequired,
|
||||
setBlocklistFilter: PropTypes.func.isRequired,
|
||||
setBlocklistTableOption: PropTypes.func.isRequired,
|
||||
clearBlocklist: PropTypes.func.isRequired,
|
||||
executeCommand: PropTypes.func.isRequired
|
||||
|
||||
54
frontend/src/Activity/Blocklist/BlocklistFilterModal.tsx
Normal file
54
frontend/src/Activity/Blocklist/BlocklistFilterModal.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import FilterModal from 'Components/Filter/FilterModal';
|
||||
import { setBlocklistFilter } from 'Store/Actions/blocklistActions';
|
||||
|
||||
function createBlocklistSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.blocklist.items,
|
||||
(blocklistItems) => {
|
||||
return blocklistItems;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createFilterBuilderPropsSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.blocklist.filterBuilderProps,
|
||||
(filterBuilderProps) => {
|
||||
return filterBuilderProps;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
interface BlocklistFilterModalProps {
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
export default function BlocklistFilterModal(props: BlocklistFilterModalProps) {
|
||||
const sectionItems = useSelector(createBlocklistSelector());
|
||||
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
|
||||
const customFilterType = 'blocklist';
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const dispatchSetFilter = useCallback(
|
||||
(payload: unknown) => {
|
||||
dispatch(setBlocklistFilter(payload));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<FilterModal
|
||||
// TODO: Don't spread all the props
|
||||
{...props}
|
||||
sectionItems={sectionItems}
|
||||
filterBuilderProps={filterBuilderProps}
|
||||
customFilterType={customFilterType}
|
||||
dispatchSetFilter={dispatchSetFilter}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -12,11 +12,10 @@ function App({ store, history }) {
|
||||
<DocumentTitle title={window.Sonarr.instanceName}>
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>
|
||||
<ApplyTheme>
|
||||
<PageConnector>
|
||||
<AppRoutes app={App} />
|
||||
</PageConnector>
|
||||
</ApplyTheme>
|
||||
<ApplyTheme />
|
||||
<PageConnector>
|
||||
<AppRoutes app={App} />
|
||||
</PageConnector>
|
||||
</ConnectedRouter>
|
||||
</Provider>
|
||||
</DocumentTitle>
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Fragment, useCallback, useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import themes from 'Styles/Themes';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.ui.item.theme || window.Sonarr.theme,
|
||||
(
|
||||
theme
|
||||
) => {
|
||||
return {
|
||||
theme
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function ApplyTheme({ theme, children }) {
|
||||
// Update the CSS Variables
|
||||
|
||||
const updateCSSVariables = useCallback(() => {
|
||||
const arrayOfVariableKeys = Object.keys(themes[theme]);
|
||||
const arrayOfVariableValues = Object.values(themes[theme]);
|
||||
|
||||
// Loop through each array key and set the CSS Variables
|
||||
arrayOfVariableKeys.forEach((cssVariableKey, index) => {
|
||||
// Based on our snippet from MDN
|
||||
document.documentElement.style.setProperty(
|
||||
`--${cssVariableKey}`,
|
||||
arrayOfVariableValues[index]
|
||||
);
|
||||
});
|
||||
}, [theme]);
|
||||
|
||||
// On Component Mount and Component Update
|
||||
useEffect(() => {
|
||||
updateCSSVariables(theme);
|
||||
}, [updateCSSVariables, theme]);
|
||||
|
||||
return <Fragment>{children}</Fragment>;
|
||||
}
|
||||
|
||||
ApplyTheme.propTypes = {
|
||||
theme: PropTypes.string.isRequired,
|
||||
children: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps)(ApplyTheme);
|
||||
37
frontend/src/App/ApplyTheme.tsx
Normal file
37
frontend/src/App/ApplyTheme.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React, { Fragment, ReactNode, useCallback, useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import themes from 'Styles/Themes';
|
||||
import AppState from './State/AppState';
|
||||
|
||||
interface ApplyThemeProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
function createThemeSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.settings.ui.item.theme || window.Sonarr.theme,
|
||||
(theme) => {
|
||||
return theme;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function ApplyTheme({ children }: ApplyThemeProps) {
|
||||
const theme = useSelector(createThemeSelector());
|
||||
|
||||
const updateCSSVariables = useCallback(() => {
|
||||
Object.entries(themes[theme]).forEach(([key, value]) => {
|
||||
document.documentElement.style.setProperty(`--${key}`, value);
|
||||
});
|
||||
}, [theme]);
|
||||
|
||||
// On Component Mount and Component Update
|
||||
useEffect(() => {
|
||||
updateCSSVariables();
|
||||
}, [updateCSSVariables, theme]);
|
||||
|
||||
return <Fragment>{children}</Fragment>;
|
||||
}
|
||||
|
||||
export default ApplyTheme;
|
||||
@@ -1,4 +1,5 @@
|
||||
import InteractiveImportAppState from 'App/State/InteractiveImportAppState';
|
||||
import BlocklistAppState from './BlocklistAppState';
|
||||
import CalendarAppState from './CalendarAppState';
|
||||
import CommandAppState from './CommandAppState';
|
||||
import EpisodeFilesAppState from './EpisodeFilesAppState';
|
||||
@@ -54,6 +55,7 @@ export interface AppSectionState {
|
||||
|
||||
interface AppState {
|
||||
app: AppSectionState;
|
||||
blocklist: BlocklistAppState;
|
||||
calendar: CalendarAppState;
|
||||
commands: CommandAppState;
|
||||
episodeFiles: EpisodeFilesAppState;
|
||||
|
||||
8
frontend/src/App/State/BlocklistAppState.ts
Normal file
8
frontend/src/App/State/BlocklistAppState.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import Blocklist from 'typings/Blocklist';
|
||||
import AppSectionState, { AppSectionFilterState } from './AppSectionState';
|
||||
|
||||
interface BlocklistAppState
|
||||
extends AppSectionState<Blocklist>,
|
||||
AppSectionFilterState<Blocklist> {}
|
||||
|
||||
export default BlocklistAppState;
|
||||
@@ -1,5 +0,0 @@
|
||||
.input {
|
||||
composes: input from '~Components/Form/TextInput.css';
|
||||
|
||||
font-family: $passwordFamily;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'input': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
||||
@@ -1,7 +1,5 @@
|
||||
import PropTypes from 'prop-types';
|
||||
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) {
|
||||
@@ -13,17 +11,14 @@ function PasswordInput(props) {
|
||||
return (
|
||||
<TextInput
|
||||
{...props}
|
||||
type="password"
|
||||
onCopy={onCopy}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
PasswordInput.propTypes = {
|
||||
className: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
PasswordInput.defaultProps = {
|
||||
className: styles.input
|
||||
...TextInput.props
|
||||
};
|
||||
|
||||
export default PasswordInput;
|
||||
|
||||
@@ -244,7 +244,7 @@ class SignalRConnector extends Component {
|
||||
handleWantedCutoff = (body) => {
|
||||
if (body.action === 'updated') {
|
||||
this.props.dispatchUpdateItem({
|
||||
section: 'cutoffUnmet',
|
||||
section: 'wanted.cutoffUnmet',
|
||||
updateOnly: true,
|
||||
...body.resource
|
||||
});
|
||||
@@ -254,7 +254,7 @@ class SignalRConnector extends Component {
|
||||
handleWantedMissing = (body) => {
|
||||
if (body.action === 'updated') {
|
||||
this.props.dispatchUpdateItem({
|
||||
section: 'missing',
|
||||
section: 'wanted.missing',
|
||||
updateOnly: true,
|
||||
...body.resource
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ class RelativeDateCell extends PureComponent {
|
||||
className,
|
||||
date,
|
||||
includeSeconds,
|
||||
includeTime,
|
||||
showRelativeDates,
|
||||
shortDateFormat,
|
||||
longDateFormat,
|
||||
@@ -39,7 +40,7 @@ class RelativeDateCell extends PureComponent {
|
||||
title={formatDateTime(date, longDateFormat, timeFormat, { includeSeconds, includeRelativeDay: !showRelativeDates })}
|
||||
{...otherProps}
|
||||
>
|
||||
{getRelativeDate(date, shortDateFormat, showRelativeDates, { timeFormat, includeSeconds, timeForToday: true })}
|
||||
{getRelativeDate(date, shortDateFormat, showRelativeDates, { timeFormat, includeSeconds, includeTime, timeForToday: true })}
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
@@ -49,6 +50,7 @@ RelativeDateCell.propTypes = {
|
||||
className: PropTypes.string.isRequired,
|
||||
date: PropTypes.string,
|
||||
includeSeconds: PropTypes.bool.isRequired,
|
||||
includeTime: PropTypes.bool.isRequired,
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
longDateFormat: PropTypes.string.isRequired,
|
||||
@@ -60,6 +62,7 @@ RelativeDateCell.propTypes = {
|
||||
RelativeDateCell.defaultProps = {
|
||||
className: styles.cell,
|
||||
includeSeconds: false,
|
||||
includeTime: false,
|
||||
component: TableRowCell
|
||||
};
|
||||
|
||||
|
||||
@@ -25,14 +25,3 @@
|
||||
font-family: 'Ubuntu Mono';
|
||||
src: url('UbuntuMono-Regular.eot?#iefix&v=1.3.0') format('embedded-opentype'), url('UbuntuMono-Regular.woff?v=1.3.0') format('woff'), url('UbuntuMono-Regular.ttf?v=1.3.0') format('truetype');
|
||||
}
|
||||
|
||||
/*
|
||||
* text-security-disc
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-family: 'text-security-disc';
|
||||
src: url('text-security-disc.woff?v=1.3.0') format('woff'), url('text-security-disc.ttf?v=1.3.0') format('truetype');
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -111,6 +111,8 @@ class EpisodeHistoryRow extends Component {
|
||||
|
||||
<RelativeDateCellConnector
|
||||
date={date}
|
||||
includeSeconds={true}
|
||||
includeTime={true}
|
||||
/>
|
||||
|
||||
<TableRowCell className={styles.actions}>
|
||||
|
||||
@@ -17,6 +17,12 @@
|
||||
width: 175px;
|
||||
}
|
||||
|
||||
.customFormatScore {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 65px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'actions': string;
|
||||
'customFormatScore': string;
|
||||
'customFormats': string;
|
||||
'languages': string;
|
||||
'quality': string;
|
||||
|
||||
@@ -11,6 +11,7 @@ import EpisodeLanguages from 'Episode/EpisodeLanguages';
|
||||
import EpisodeQuality from 'Episode/EpisodeQuality';
|
||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import MediaInfo from './MediaInfo';
|
||||
import styles from './EpisodeFileRow.css';
|
||||
@@ -55,6 +56,7 @@ class EpisodeFileRow extends Component {
|
||||
languages,
|
||||
quality,
|
||||
customFormats,
|
||||
customFormatScore,
|
||||
qualityCutoffNotMet,
|
||||
mediaInfo,
|
||||
columns
|
||||
@@ -127,6 +129,17 @@ class EpisodeFileRow extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'customFormatScore') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.customFormatScore}
|
||||
>
|
||||
{formatCustomFormatScore(customFormatScore, customFormats.length)}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'actions') {
|
||||
return (
|
||||
<TableRowCell
|
||||
@@ -183,6 +196,7 @@ EpisodeFileRow.propTypes = {
|
||||
quality: PropTypes.object.isRequired,
|
||||
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
||||
customFormats: PropTypes.arrayOf(PropTypes.object),
|
||||
customFormatScore: PropTypes.number.isRequired,
|
||||
mediaInfo: PropTypes.object,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onDeleteEpisodeFile: PropTypes.func.isRequired
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import Label from 'Components/Label';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import { kinds, sizes } from 'Helpers/Props';
|
||||
import { icons, kinds, sizes } from 'Helpers/Props';
|
||||
import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import EpisodeAiringConnector from './EpisodeAiringConnector';
|
||||
@@ -42,6 +43,15 @@ const columns = [
|
||||
isSortable: false,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'customFormatScore',
|
||||
label: React.createElement(Icon, {
|
||||
name: icons.SCORE,
|
||||
title: () => translate('CustomFormatScore')
|
||||
}),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
label: '',
|
||||
@@ -94,6 +104,7 @@ class EpisodeSummary extends Component {
|
||||
languages,
|
||||
quality,
|
||||
customFormats,
|
||||
customFormatScore,
|
||||
qualityCutoffNotMet,
|
||||
onDeleteEpisodeFile
|
||||
} = this.props;
|
||||
@@ -143,6 +154,7 @@ class EpisodeSummary extends Component {
|
||||
quality={quality}
|
||||
qualityCutoffNotMet={qualityCutoffNotMet}
|
||||
customFormats={customFormats}
|
||||
customFormatScore={customFormatScore}
|
||||
mediaInfo={mediaInfo}
|
||||
columns={columns}
|
||||
onDeleteEpisodeFile={onDeleteEpisodeFile}
|
||||
@@ -179,6 +191,7 @@ EpisodeSummary.propTypes = {
|
||||
quality: PropTypes.object,
|
||||
qualityCutoffNotMet: PropTypes.bool,
|
||||
customFormats: PropTypes.arrayOf(PropTypes.object),
|
||||
customFormatScore: PropTypes.number.isRequired,
|
||||
onDeleteEpisodeFile: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -31,7 +31,8 @@ function createMapStateToProps() {
|
||||
languages,
|
||||
quality,
|
||||
qualityCutoffNotMet,
|
||||
customFormats
|
||||
customFormats,
|
||||
customFormatScore
|
||||
} = episodeFile;
|
||||
|
||||
return {
|
||||
@@ -45,7 +46,8 @@ function createMapStateToProps() {
|
||||
languages,
|
||||
quality,
|
||||
qualityCutoffNotMet,
|
||||
customFormats
|
||||
customFormats,
|
||||
customFormatScore
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@ function SeriesHistoryModal(props) {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
size={sizes.EXTRA_LARGE}
|
||||
size={sizes.EXTRA_EXTRA_LARGE}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<SeriesHistoryModalContentConnector
|
||||
|
||||
@@ -135,6 +135,8 @@ class SeriesHistoryRow extends Component {
|
||||
|
||||
<RelativeDateCellConnector
|
||||
date={date}
|
||||
includeSeconds={true}
|
||||
includeTime={true}
|
||||
/>
|
||||
|
||||
<TableRowCell className={styles.actions}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
import { batchActions } from 'redux-batched-actions';
|
||||
import { sortDirections } from 'Helpers/Props';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
|
||||
@@ -77,6 +77,31 @@ export const defaultState = {
|
||||
isVisible: true,
|
||||
isModifiable: false
|
||||
}
|
||||
],
|
||||
|
||||
selectedFilterKey: 'all',
|
||||
|
||||
filters: [
|
||||
{
|
||||
key: 'all',
|
||||
label: () => translate('All'),
|
||||
filters: []
|
||||
}
|
||||
],
|
||||
|
||||
filterBuilderProps: [
|
||||
{
|
||||
name: 'seriesIds',
|
||||
label: () => translate('Series'),
|
||||
type: filterBuilderTypes.EQUAL,
|
||||
valueType: filterBuilderValueTypes.SERIES
|
||||
},
|
||||
{
|
||||
name: 'protocols',
|
||||
label: () => translate('Protocol'),
|
||||
type: filterBuilderTypes.EQUAL,
|
||||
valueType: filterBuilderValueTypes.PROTOCOL
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -84,6 +109,7 @@ export const persistState = [
|
||||
'blocklist.pageSize',
|
||||
'blocklist.sortKey',
|
||||
'blocklist.sortDirection',
|
||||
'blocklist.selectedFilterKey',
|
||||
'blocklist.columns'
|
||||
];
|
||||
|
||||
@@ -97,6 +123,7 @@ export const GOTO_NEXT_BLOCKLIST_PAGE = 'blocklist/gotoBlocklistNextPage';
|
||||
export const GOTO_LAST_BLOCKLIST_PAGE = 'blocklist/gotoBlocklistLastPage';
|
||||
export const GOTO_BLOCKLIST_PAGE = 'blocklist/gotoBlocklistPage';
|
||||
export const SET_BLOCKLIST_SORT = 'blocklist/setBlocklistSort';
|
||||
export const SET_BLOCKLIST_FILTER = 'blocklist/setBlocklistFilter';
|
||||
export const SET_BLOCKLIST_TABLE_OPTION = 'blocklist/setBlocklistTableOption';
|
||||
export const REMOVE_BLOCKLIST_ITEM = 'blocklist/removeBlocklistItem';
|
||||
export const REMOVE_BLOCKLIST_ITEMS = 'blocklist/removeBlocklistItems';
|
||||
@@ -112,6 +139,7 @@ export const gotoBlocklistNextPage = createThunk(GOTO_NEXT_BLOCKLIST_PAGE);
|
||||
export const gotoBlocklistLastPage = createThunk(GOTO_LAST_BLOCKLIST_PAGE);
|
||||
export const gotoBlocklistPage = createThunk(GOTO_BLOCKLIST_PAGE);
|
||||
export const setBlocklistSort = createThunk(SET_BLOCKLIST_SORT);
|
||||
export const setBlocklistFilter = createThunk(SET_BLOCKLIST_FILTER);
|
||||
export const setBlocklistTableOption = createAction(SET_BLOCKLIST_TABLE_OPTION);
|
||||
export const removeBlocklistItem = createThunk(REMOVE_BLOCKLIST_ITEM);
|
||||
export const removeBlocklistItems = createThunk(REMOVE_BLOCKLIST_ITEMS);
|
||||
@@ -132,7 +160,8 @@ export const actionHandlers = handleThunks({
|
||||
[serverSideCollectionHandlers.NEXT_PAGE]: GOTO_NEXT_BLOCKLIST_PAGE,
|
||||
[serverSideCollectionHandlers.LAST_PAGE]: GOTO_LAST_BLOCKLIST_PAGE,
|
||||
[serverSideCollectionHandlers.EXACT_PAGE]: GOTO_BLOCKLIST_PAGE,
|
||||
[serverSideCollectionHandlers.SORT]: SET_BLOCKLIST_SORT
|
||||
[serverSideCollectionHandlers.SORT]: SET_BLOCKLIST_SORT,
|
||||
[serverSideCollectionHandlers.FILTER]: SET_BLOCKLIST_FILTER
|
||||
}),
|
||||
|
||||
[REMOVE_BLOCKLIST_ITEM]: createRemoveItemHandler(section, '/blocklist'),
|
||||
|
||||
@@ -192,6 +192,22 @@ export const filterPredicates = {
|
||||
});
|
||||
|
||||
return predicate(hasMissingSeason, filterValue);
|
||||
},
|
||||
|
||||
hasUnmonitoredSeason: function(item, filterValue, type) {
|
||||
const predicate = filterTypePredicates[type];
|
||||
const { seasons = [] } = item;
|
||||
|
||||
const hasUnmonitoredSeason = seasons.some((season) => {
|
||||
const {
|
||||
seasonNumber,
|
||||
monitored
|
||||
} = season;
|
||||
|
||||
return seasonNumber > 0 && !monitored;
|
||||
});
|
||||
|
||||
return predicate(hasUnmonitoredSeason, filterValue);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -353,6 +369,12 @@ export const filterBuilderProps = [
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.BOOL
|
||||
},
|
||||
{
|
||||
name: 'hasUnmonitoredSeason',
|
||||
label: () => translate('HasUnmonitoredSeason'),
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.BOOL
|
||||
},
|
||||
{
|
||||
name: 'year',
|
||||
label: () => translate('Year'),
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as dark from './dark';
|
||||
import * as light from './light';
|
||||
|
||||
const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const auto = defaultDark ? { ...dark } : { ...light };
|
||||
const auto = defaultDark ? dark : light;
|
||||
|
||||
export default {
|
||||
auto,
|
||||
|
||||
@@ -2,7 +2,6 @@ module.exports = {
|
||||
// Families
|
||||
defaultFontFamily: 'Roboto, "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif',
|
||||
monoSpaceFontFamily: '"Ubuntu Mono", Menlo, Monaco, Consolas, "Courier New", monospace;',
|
||||
passwordFamily: 'text-security-disc',
|
||||
|
||||
// Sizes
|
||||
extraSmallFontSize: '11px',
|
||||
|
||||
@@ -5,16 +5,18 @@ import isToday from 'Utilities/Date/isToday';
|
||||
import isTomorrow from 'Utilities/Date/isTomorrow';
|
||||
import isYesterday from 'Utilities/Date/isYesterday';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import formatDateTime from './formatDateTime';
|
||||
|
||||
function getRelativeDate(date, shortDateFormat, showRelativeDates, { timeFormat, includeSeconds = false, timeForToday = false } = {}) {
|
||||
function getRelativeDate(date, shortDateFormat, showRelativeDates, { timeFormat, includeSeconds = false, timeForToday = false, includeTime = false } = {}) {
|
||||
if (!date) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isTodayDate = isToday(date);
|
||||
const time = formatTime(date, timeFormat, { includeMinuteZero: true, includeSeconds });
|
||||
|
||||
if (isTodayDate && timeForToday && timeFormat) {
|
||||
return formatTime(date, timeFormat, { includeMinuteZero: true, includeSeconds });
|
||||
return time;
|
||||
}
|
||||
|
||||
if (!showRelativeDates) {
|
||||
@@ -22,22 +24,26 @@ function getRelativeDate(date, shortDateFormat, showRelativeDates, { timeFormat,
|
||||
}
|
||||
|
||||
if (isYesterday(date)) {
|
||||
return translate('Yesterday');
|
||||
return includeTime ? translate('YesterdayAt', { time } ): translate('Yesterday');
|
||||
}
|
||||
|
||||
if (isTodayDate) {
|
||||
return translate('Today');
|
||||
return includeTime ? translate('TodayAt', { time } ): translate('Today');
|
||||
}
|
||||
|
||||
if (isTomorrow(date)) {
|
||||
return translate('Tomorrow');
|
||||
return includeTime ? translate('TomorrowAt', { time } ): translate('Tomorrow');
|
||||
}
|
||||
|
||||
if (isInNextWeek(date)) {
|
||||
return moment(date).format('dddd');
|
||||
const day = moment(date).format('dddd');
|
||||
|
||||
return includeTime ? translate('DayOfWeekAt', { day, time }) : day;
|
||||
}
|
||||
|
||||
return moment(date).format(shortDateFormat);
|
||||
return includeTime ?
|
||||
formatDateTime(date, shortDateFormat, timeFormat, { includeSeconds }) :
|
||||
moment(date).format(shortDateFormat);
|
||||
}
|
||||
|
||||
export default getRelativeDate;
|
||||
|
||||
@@ -57,8 +57,8 @@
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-color: #f5f7fa;
|
||||
color: #656565;
|
||||
background-color: var(--pageBackground);
|
||||
color: var(--textColor);
|
||||
font-family: "Roboto", "open sans", "Helvetica Neue", Helvetica, Arial,
|
||||
sans-serif;
|
||||
}
|
||||
@@ -88,14 +88,14 @@
|
||||
padding: 10px;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
background-color: #3a3f51;
|
||||
background-color: var(--themeDarkColor);
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
padding: 20px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
background-color: #fff;
|
||||
background-color: var(--panelBackground);
|
||||
}
|
||||
|
||||
.sign-in {
|
||||
@@ -112,16 +112,18 @@
|
||||
padding: 6px 16px;
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
border: 1px solid #dde6e9;
|
||||
background-color: var(--inputBackgroundColor);
|
||||
border: 1px solid var(--inputBorderColor);
|
||||
border-radius: 4px;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
box-shadow: inset 0 1px 1px var(--inputBoxShadowColor);
|
||||
color: var(--textColor);
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
outline: 0;
|
||||
border-color: #66afe9;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
|
||||
0 0 8px rgba(102, 175, 233, 0.6);
|
||||
border-color: var(--inputFocusBorderColor);
|
||||
box-shadow: inset 0 1px 1px var(--inputBoxShadowColor),
|
||||
0 0 8px var(--inputFocusBoxShadowColor);
|
||||
}
|
||||
|
||||
.button {
|
||||
@@ -130,10 +132,10 @@
|
||||
padding: 10px 0;
|
||||
width: 100%;
|
||||
border: 1px solid;
|
||||
border-color: #5899eb;
|
||||
border-color: var(--primaryBorderColor);
|
||||
border-radius: 4px;
|
||||
background-color: #5d9cec;
|
||||
color: #fff;
|
||||
background-color: var(--primaryBackgroundColor);
|
||||
color: var(--white);
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
@@ -141,9 +143,9 @@
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
border-color: #3483e7;
|
||||
background-color: #4b91ea;
|
||||
color: #fff;
|
||||
border-color: var(--primaryHoverBorderColor);
|
||||
background-color: var(--primaryHoverBackgroundColor);
|
||||
color: var(--white);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -165,24 +167,24 @@
|
||||
|
||||
.forgot-password {
|
||||
margin-left: auto;
|
||||
color: #909fa7;
|
||||
color: var(--forgotPasswordColor);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.forgot-password:focus,
|
||||
.forgot-password:hover {
|
||||
color: #748690;
|
||||
color: var(--forgotPasswordAltColor);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.forgot-password:visited {
|
||||
color: #748690;
|
||||
color: var(--forgotPasswordAltColor);
|
||||
}
|
||||
|
||||
.login-failed {
|
||||
margin-top: 20px;
|
||||
color: #f05050;
|
||||
color: var(--failedColor);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@@ -291,5 +293,59 @@
|
||||
|
||||
loginFailedDiv.classList.remove("hidden");
|
||||
}
|
||||
|
||||
var light = {
|
||||
white: '#fff',
|
||||
pageBackground: '#f5f7fa',
|
||||
textColor: '#515253',
|
||||
themeDarkColor: '#3a3f51',
|
||||
panelBackground: '#fff',
|
||||
inputBackgroundColor: '#fff',
|
||||
inputBorderColor: '#dde6e9',
|
||||
inputBoxShadowColor: 'rgba(0, 0, 0, 0.075)',
|
||||
inputFocusBorderColor: '#66afe9',
|
||||
inputFocusBoxShadowColor: 'rgba(102, 175, 233, 0.6)',
|
||||
primaryBackgroundColor: '#5d9cec',
|
||||
primaryBorderColor: '#5899eb',
|
||||
primaryHoverBackgroundColor: '#4b91ea',
|
||||
primaryHoverBorderColor: '#3483e7',
|
||||
failedColor: '#f05050',
|
||||
forgotPasswordColor: '#909fa7',
|
||||
forgotPasswordAltColor: '#748690'
|
||||
};
|
||||
|
||||
var dark = {
|
||||
white: '#fff',
|
||||
pageBackground: '#202020',
|
||||
textColor: '#ccc',
|
||||
themeDarkColor: '#494949',
|
||||
panelBackground: '#111',
|
||||
inputBackgroundColor: '#333',
|
||||
inputBorderColor: '#dde6e9',
|
||||
inputBoxShadowColor: 'rgba(0, 0, 0, 0.075)',
|
||||
inputFocusBorderColor: '#66afe9',
|
||||
inputFocusBoxShadowColor: 'rgba(102, 175, 233, 0.6)',
|
||||
primaryBackgroundColor: '#5d9cec',
|
||||
primaryBorderColor: '#5899eb',
|
||||
primaryHoverBackgroundColor: '#4b91ea',
|
||||
primaryHoverBorderColor: '#3483e7',
|
||||
failedColor: '#f05050',
|
||||
forgotPasswordColor: '#737d83',
|
||||
forgotPasswordAltColor: '#546067'
|
||||
};
|
||||
|
||||
var theme = "_THEME_";
|
||||
var defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
var finalTheme = theme === 'dark' || (theme === 'auto' && defaultDark) ?
|
||||
dark :
|
||||
light;
|
||||
|
||||
Object.entries(finalTheme).forEach(([key, value]) => {
|
||||
document.documentElement.style.setProperty(
|
||||
`--${key}`,
|
||||
value
|
||||
);
|
||||
});
|
||||
|
||||
</script>
|
||||
</html>
|
||||
|
||||
16
frontend/src/typings/Blocklist.ts
Normal file
16
frontend/src/typings/Blocklist.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import ModelBase from 'App/ModelBase';
|
||||
import Language from 'Language/Language';
|
||||
import { QualityModel } from 'Quality/Quality';
|
||||
import CustomFormat from 'typings/CustomFormat';
|
||||
|
||||
interface Blocklist extends ModelBase {
|
||||
languages: Language[];
|
||||
quality: QualityModel;
|
||||
customFormats: CustomFormat[];
|
||||
title: string;
|
||||
date?: string;
|
||||
protocol: string;
|
||||
seriesId?: number;
|
||||
}
|
||||
|
||||
export default Blocklist;
|
||||
@@ -1,5 +1,5 @@
|
||||
export interface UiSettings {
|
||||
theme: string;
|
||||
theme: 'auto' | 'dark' | 'light';
|
||||
showRelativeDates: boolean;
|
||||
shortDateFormat: string;
|
||||
longDateFormat: string;
|
||||
|
||||
@@ -178,8 +178,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
VerifyWarning(item);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void paused_item_should_have_required_properties()
|
||||
[TestCase("pausedDL")]
|
||||
[TestCase("stoppedDL")]
|
||||
public void paused_item_should_have_required_properties(string state)
|
||||
{
|
||||
var torrent = new QBittorrentTorrent
|
||||
{
|
||||
@@ -188,7 +189,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
Size = 1000,
|
||||
Progress = 0.7,
|
||||
Eta = 8640000,
|
||||
State = "pausedDL",
|
||||
State = state,
|
||||
Label = "",
|
||||
SavePath = ""
|
||||
};
|
||||
@@ -200,6 +201,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
[TestCase("queuedUP")]
|
||||
[TestCase("uploading")]
|
||||
[TestCase("stalledUP")]
|
||||
@@ -418,8 +420,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
result.OutputPath.FullPath.Should().Be(Path.Combine(torrent.SavePath, "Droned.S01.12"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void api_261_should_use_content_path()
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void api_261_should_use_content_path(string state)
|
||||
{
|
||||
var torrent = new QBittorrentTorrent
|
||||
{
|
||||
@@ -428,7 +431,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
Size = 1000,
|
||||
Progress = 0.7,
|
||||
Eta = 8640000,
|
||||
State = "pausedUP",
|
||||
State = state,
|
||||
Label = "",
|
||||
SavePath = @"C:\Torrents".AsOsAgnostic(),
|
||||
ContentPath = @"C:\Torrents\Droned.S01.12".AsOsAgnostic()
|
||||
@@ -657,44 +660,48 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
item.CanMoveFiles.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_be_removable_and_should_not_allow_move_files_if_max_ratio_is_not_set()
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_not_be_removable_and_should_not_allow_move_files_if_max_ratio_is_not_set(string state)
|
||||
{
|
||||
GivenGlobalSeedLimits(-1);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 1.0f);
|
||||
GivenCompletedTorrent(state, ratio: 1.0f);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeFalse();
|
||||
item.CanMoveFiles.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_ratio_reached_and_paused()
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_ratio_reached_and_paused(string state)
|
||||
{
|
||||
GivenGlobalSeedLimits(1.0f);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 1.0f);
|
||||
GivenCompletedTorrent(state, ratio: 1.0f);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_removable_and_should_allow_move_files_if_overridden_max_ratio_reached_and_paused()
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_overridden_max_ratio_reached_and_paused(string state)
|
||||
{
|
||||
GivenGlobalSeedLimits(2.0f);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 1.0f, ratioLimit: 0.8f);
|
||||
GivenCompletedTorrent(state, ratio: 1.0f, ratioLimit: 0.8f);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_be_removable_if_overridden_max_ratio_not_reached_and_paused()
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_not_be_removable_if_overridden_max_ratio_not_reached_and_paused(string state)
|
||||
{
|
||||
GivenGlobalSeedLimits(0.2f);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 0.5f, ratioLimit: 0.8f);
|
||||
GivenCompletedTorrent(state, ratio: 0.5f, ratioLimit: 0.8f);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeFalse();
|
||||
@@ -712,33 +719,36 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
item.CanMoveFiles.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_and_paused()
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_and_paused(string state)
|
||||
{
|
||||
GivenGlobalSeedLimits(-1, 20);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20);
|
||||
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_removable_and_should_allow_move_files_if_overridden_max_seedingtime_reached_and_paused()
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_overridden_max_seedingtime_reached_and_paused(string state)
|
||||
{
|
||||
GivenGlobalSeedLimits(-1, 40);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20, seedingTimeLimit: 10);
|
||||
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20, seedingTimeLimit: 10);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_be_removable_if_overridden_max_seedingtime_not_reached_and_paused()
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_not_be_removable_if_overridden_max_seedingtime_not_reached_and_paused(string state)
|
||||
{
|
||||
GivenGlobalSeedLimits(-1, 20);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 30, seedingTimeLimit: 40);
|
||||
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 30, seedingTimeLimit: 40);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeFalse();
|
||||
@@ -756,66 +766,72 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
item.CanMoveFiles.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_and_paused()
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_and_paused(string state)
|
||||
{
|
||||
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 2.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds());
|
||||
GivenCompletedTorrent(state, ratio: 2.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds());
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_removable_and_should_allow_move_files_if_overridden_max_inactive_seedingtime_reached_and_paused()
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_overridden_max_inactive_seedingtime_reached_and_paused(string state)
|
||||
{
|
||||
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 40);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20, inactiveSeedingTimeLimit: 10, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(15)).ToUnixTimeSeconds());
|
||||
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20, inactiveSeedingTimeLimit: 10, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(15)).ToUnixTimeSeconds());
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_be_removable_if_overridden_max_inactive_seedingtime_not_reached_and_paused()
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_not_be_removable_if_overridden_max_inactive_seedingtime_not_reached_and_paused(string state)
|
||||
{
|
||||
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 30, inactiveSeedingTimeLimit: 40, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(30)).ToUnixTimeSeconds());
|
||||
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 30, inactiveSeedingTimeLimit: 40, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(30)).ToUnixTimeSeconds());
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeFalse();
|
||||
item.CanMoveFiles.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_but_ratio_not_and_paused()
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_but_ratio_not_and_paused(string state)
|
||||
{
|
||||
GivenGlobalSeedLimits(2.0f, 20);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 1.0f, seedingTime: 30);
|
||||
GivenCompletedTorrent(state, ratio: 1.0f, seedingTime: 30);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_but_ratio_not_and_paused()
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_but_ratio_not_and_paused(string state)
|
||||
{
|
||||
GivenGlobalSeedLimits(2.0f, maxInactiveSeedingTime: 20);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 1.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds());
|
||||
GivenCompletedTorrent(state, ratio: 1.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds());
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_fetch_details_twice()
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_not_fetch_details_twice(string state)
|
||||
{
|
||||
GivenGlobalSeedLimits(-1, 30);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20);
|
||||
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeFalse();
|
||||
@@ -827,8 +843,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
.Verify(p => p.GetTorrentProperties(It.IsAny<string>(), It.IsAny<QBittorrentSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_get_category_from_the_category_if_set()
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_get_category_from_the_category_if_set(string state)
|
||||
{
|
||||
const string category = "tv-sonarr";
|
||||
GivenGlobalSeedLimits(1.0f);
|
||||
@@ -840,7 +857,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
Size = 1000,
|
||||
Progress = 1.0,
|
||||
Eta = 8640000,
|
||||
State = "pausedUP",
|
||||
State = state,
|
||||
Category = category,
|
||||
SavePath = "",
|
||||
Ratio = 1.0f
|
||||
@@ -852,8 +869,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
item.Category.Should().Be(category);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_get_category_from_the_label_if_the_category_is_not_available()
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_get_category_from_the_label_if_the_category_is_not_available(string state)
|
||||
{
|
||||
const string category = "tv-sonarr";
|
||||
GivenGlobalSeedLimits(1.0f);
|
||||
@@ -865,7 +883,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
Size = 1000,
|
||||
Progress = 1.0,
|
||||
Eta = 8640000,
|
||||
State = "pausedUP",
|
||||
State = state,
|
||||
Label = category,
|
||||
SavePath = "",
|
||||
Ratio = 1.0f
|
||||
|
||||
@@ -5,7 +5,6 @@ using FluentValidation.Results;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Notifications;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.Validation;
|
||||
using NzbDrone.Test.Common;
|
||||
@@ -15,9 +14,9 @@ namespace NzbDrone.Core.Test.NotificationTests
|
||||
[TestFixture]
|
||||
public class NotificationBaseFixture : TestBase
|
||||
{
|
||||
private class TestSetting : IProviderConfig
|
||||
private class TestSetting : NotificationSettingsBase<TestSetting>
|
||||
{
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult();
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("2019_08_20_1080_all.mp4", "", 2019, 8, 20)]
|
||||
[TestCase("Series and Title 20201013 Ep7432 [720p WebRip (x264)] [SUBS]", "Series and Title", 2020, 10, 13)]
|
||||
[TestCase("Series Title (1955) - 1954-01-23 05 00 00 - Cottage for Sale.ts", "Series Title (1955)", 1954, 1, 23)]
|
||||
[TestCase("Series Title - 30-04-2024 HDTV 1080p H264 AAC", "Series Title", 2024, 4, 30)]
|
||||
|
||||
// [TestCase("", "", 0, 0, 0)]
|
||||
public void should_parse_daily_episode(string postTitle, string title, int year, int month, int day)
|
||||
@@ -100,5 +101,13 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
|
||||
Parser.Parser.ParseTitle(title).Should().BeNull();
|
||||
}
|
||||
|
||||
[TestCase("Tmc - Quotidien - 05-06-2024 HDTV 1080p H264 AAC")]
|
||||
|
||||
// [TestCase("", "", 0, 0, 0)]
|
||||
public void should_not_parse_ambiguous_daily_episode(string postTitle)
|
||||
{
|
||||
Parser.Parser.ParseTitle(postTitle).Should().BeNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,8 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("The.Series.S01E05.480p.BluRay.DD5.1.x264-HiSD", false)]
|
||||
[TestCase("The Series (BD)(640x480(RAW) (BATCH 1) (1-13)", false)]
|
||||
[TestCase("[Doki] Series - 02 (848x480 XviD BD MP3) [95360783]", false)]
|
||||
[TestCase("Adventures.of.Sonic.the.Hedgehog.S01.BluRay.480i.DD.2.0.AVC.REMUX-FraMeSToR", false)]
|
||||
[TestCase("Adventures.of.Sonic.the.Hedgehog.S01E01.Best.Hedgehog.480i.DD.2.0.AVC.REMUX-FraMeSToR", false)]
|
||||
public void should_parse_bluray480p_quality(string title, bool proper)
|
||||
{
|
||||
ParseAndVerifyQuality(title, Quality.Bluray480p, proper);
|
||||
@@ -309,6 +311,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Sans.Series.De.Traces.FRENCH.720p.BluRay.x264-FHD", false)]
|
||||
[TestCase("Series.Black.1x01.Selezione.Naturale.ITA.720p.BDMux.x264-NovaRip", false)]
|
||||
[TestCase("Series.Hunter.S02.720p.Blu-ray.Remux.AVC.FLAC.2.0-SiCFoI", false)]
|
||||
[TestCase("Adventures.of.Sonic.the.Hedgehog.S01E01.Best.Hedgehog.720p.DD.2.0.AVC.REMUX-FraMeSToR", false)]
|
||||
public void should_parse_bluray720p_quality(string title, bool proper)
|
||||
{
|
||||
ParseAndVerifyQuality(title, Quality.Bluray720p, proper);
|
||||
@@ -340,6 +343,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Series.Title.S03E01.The.Calm.1080p.DTS-HD.MA.5.1.AVC.REMUX-FraMeSToR", false)]
|
||||
[TestCase("Series Title Season 2 (BDRemux 1080p HEVC FLAC) [Netaro]", false)]
|
||||
[TestCase("[Vodes] Series Title - Other Title (2020) [BDRemux 1080p HEVC Dual-Audio]", false)]
|
||||
[TestCase("Adventures.of.Sonic.the.Hedgehog.S01E01.Best.Hedgehog.1080p.DD.2.0.AVC.REMUX-FraMeSToR", false)]
|
||||
public void should_parse_bluray1080p_remux_quality(string title, bool proper)
|
||||
{
|
||||
ParseAndVerifyQuality(title, Quality.Bluray1080pRemux, proper);
|
||||
@@ -360,6 +364,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Series.Title.S01E08.The.Sonarr.BluRay.2160p.AVC.DTS-HD.MA.5.1.REMUX-FraMeSToR", false)]
|
||||
[TestCase("Series.Title.2x11.Nato.Per.The.Sonarr.Bluray.Remux.AVC.2160p.AC3.ITA", false)]
|
||||
[TestCase("[Dolby Vision] Sonarr.of.Series.S07.MULTi.UHD.BLURAY.REMUX.DV-NoTag", false)]
|
||||
[TestCase("Adventures.of.Sonic.the.Hedgehog.S01E01.Best.Hedgehog.2160p.DD.2.0.AVC.REMUX-FraMeSToR", false)]
|
||||
public void should_parse_bluray2160p_remux_quality(string title, bool proper)
|
||||
{
|
||||
ParseAndVerifyQuality(title, Quality.Bluray2160pRemux, proper);
|
||||
|
||||
@@ -75,6 +75,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
|
||||
[TestCase("The.Series.2016.S02.Part.1.1080p.NF.WEBRip.DD5.1.x264-NTb", "The Series 2016", 2, 1)]
|
||||
[TestCase("The.Series.S07.Vol.1.1080p.NF.WEBRip.DD5.1.x264-NTb", "The Series", 7, 1)]
|
||||
[TestCase("The.Series.S06.P1.1080p.Blu-Ray.10-Bit.Dual-Audio.TrueHD.x265-iAHD", "The Series", 6, 1)]
|
||||
public void should_parse_partial_season_release(string postTitle, string title, int season, int seasonPart)
|
||||
{
|
||||
var result = Parser.Parser.ParseTitle(postTitle);
|
||||
|
||||
@@ -40,11 +40,29 @@ namespace NzbDrone.Core.Blocklisting
|
||||
Delete(x => seriesIds.Contains(x.SeriesId));
|
||||
}
|
||||
|
||||
protected override SqlBuilder PagedBuilder() => new SqlBuilder(_database.DatabaseType).Join<Blocklist, Series>((b, m) => b.SeriesId == m.Id);
|
||||
protected override IEnumerable<Blocklist> PagedQuery(SqlBuilder sql) => _database.QueryJoined<Blocklist, Series>(sql, (bl, movie) =>
|
||||
{
|
||||
bl.Series = movie;
|
||||
return bl;
|
||||
});
|
||||
public override PagingSpec<Blocklist> GetPaged(PagingSpec<Blocklist> pagingSpec)
|
||||
{
|
||||
pagingSpec.Records = GetPagedRecords(PagedBuilder(), pagingSpec, PagedQuery);
|
||||
|
||||
var countTemplate = $"SELECT COUNT(*) FROM (SELECT /**select**/ FROM \"{TableMapping.Mapper.TableNameMapping(typeof(Blocklist))}\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/) AS \"Inner\"";
|
||||
pagingSpec.TotalRecords = GetPagedRecordCount(PagedBuilder().Select(typeof(Blocklist)), pagingSpec, countTemplate);
|
||||
|
||||
return pagingSpec;
|
||||
}
|
||||
|
||||
protected override SqlBuilder PagedBuilder()
|
||||
{
|
||||
var builder = Builder()
|
||||
.Join<Blocklist, Series>((b, m) => b.SeriesId == m.Id);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
protected override IEnumerable<Blocklist> PagedQuery(SqlBuilder builder) =>
|
||||
_database.QueryJoined<Blocklist, Series>(builder, (blocklist, series) =>
|
||||
{
|
||||
blocklist.Series = series;
|
||||
return blocklist;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,7 +242,7 @@ namespace NzbDrone.Core.Configuration
|
||||
{
|
||||
get
|
||||
{
|
||||
var urlBase = _serverOptions.UrlBase ?? GetValue("UrlBase", "").Trim('/');
|
||||
var urlBase = (_serverOptions.UrlBase ?? GetValue("UrlBase", "")).Trim('/');
|
||||
|
||||
if (urlBase.IsNullOrWhiteSpace())
|
||||
{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Aria2
|
||||
@@ -13,9 +12,9 @@ namespace NzbDrone.Core.Download.Clients.Aria2
|
||||
}
|
||||
}
|
||||
|
||||
public class Aria2Settings : IProviderConfig
|
||||
public class Aria2Settings : DownloadClientSettingsBase<Aria2Settings>
|
||||
{
|
||||
private static readonly Aria2SettingsValidator Validator = new Aria2SettingsValidator();
|
||||
private static readonly Aria2SettingsValidator Validator = new ();
|
||||
|
||||
public Aria2Settings()
|
||||
{
|
||||
@@ -44,7 +43,7 @@ namespace NzbDrone.Core.Download.Clients.Aria2
|
||||
[FieldDefinition(5, Label = "Directory", Type = FieldType.Textbox, HelpText = "DownloadClientAriaSettingsDirectoryHelpText")]
|
||||
public string Directory { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using System.ComponentModel;
|
||||
using FluentValidation;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
using NzbDrone.Core.Validation.Paths;
|
||||
|
||||
@@ -18,7 +17,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||
}
|
||||
}
|
||||
|
||||
public class TorrentBlackholeSettings : IProviderConfig
|
||||
public class TorrentBlackholeSettings : DownloadClientSettingsBase<TorrentBlackholeSettings>
|
||||
{
|
||||
public TorrentBlackholeSettings()
|
||||
{
|
||||
@@ -26,7 +25,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||
ReadOnly = true;
|
||||
}
|
||||
|
||||
private static readonly TorrentBlackholeSettingsValidator Validator = new TorrentBlackholeSettingsValidator();
|
||||
private static readonly TorrentBlackholeSettingsValidator Validator = new ();
|
||||
|
||||
[FieldDefinition(0, Label = "TorrentBlackholeTorrentFolder", Type = FieldType.Path, HelpText = "BlackholeFolderHelpText")]
|
||||
[FieldToken(TokenField.HelpText, "TorrentBlackholeTorrentFolder", "extension", ".torrent")]
|
||||
@@ -48,7 +47,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||
[FieldDefinition(4, Label = "TorrentBlackholeSaveMagnetFilesReadOnly", Type = FieldType.Checkbox, HelpText = "TorrentBlackholeSaveMagnetFilesReadOnlyHelpText")]
|
||||
public bool ReadOnly { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using FluentValidation;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
using NzbDrone.Core.Validation.Paths;
|
||||
|
||||
@@ -15,9 +14,9 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||
}
|
||||
}
|
||||
|
||||
public class UsenetBlackholeSettings : IProviderConfig
|
||||
public class UsenetBlackholeSettings : DownloadClientSettingsBase<UsenetBlackholeSettings>
|
||||
{
|
||||
private static readonly UsenetBlackholeSettingsValidator Validator = new UsenetBlackholeSettingsValidator();
|
||||
private static readonly UsenetBlackholeSettingsValidator Validator = new ();
|
||||
|
||||
[FieldDefinition(0, Label = "UsenetBlackholeNzbFolder", Type = FieldType.Path, HelpText = "BlackholeFolderHelpText")]
|
||||
[FieldToken(TokenField.HelpText, "UsenetBlackholeNzbFolder", "extension", ".nzb")]
|
||||
@@ -26,7 +25,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||
[FieldDefinition(1, Label = "BlackholeWatchFolder", Type = FieldType.Path, HelpText = "BlackholeWatchFolderHelpText")]
|
||||
public string WatchFolder { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
@@ -17,9 +16,9 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
}
|
||||
}
|
||||
|
||||
public class DelugeSettings : IProviderConfig
|
||||
public class DelugeSettings : DownloadClientSettingsBase<DelugeSettings>
|
||||
{
|
||||
private static readonly DelugeSettingsValidator Validator = new DelugeSettingsValidator();
|
||||
private static readonly DelugeSettingsValidator Validator = new ();
|
||||
|
||||
public DelugeSettings()
|
||||
{
|
||||
@@ -67,7 +66,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
[FieldDefinition(11, Label = "DownloadClientDelugeSettingsDirectoryCompleted", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientDelugeSettingsDirectoryCompletedHelpText")]
|
||||
public string CompletedDirectory { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using Equ;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients
|
||||
{
|
||||
public abstract class DownloadClientSettingsBase<TSettings> : IProviderConfig, IEquatable<TSettings>
|
||||
where TSettings : DownloadClientSettingsBase<TSettings>
|
||||
{
|
||||
private static readonly MemberwiseEqualityComparer<TSettings> Comparer = MemberwiseEqualityComparer<TSettings>.ByProperties;
|
||||
|
||||
public abstract NzbDroneValidationResult Validate();
|
||||
|
||||
public bool Equals(TSettings other)
|
||||
{
|
||||
return Comparer.Equals(this as TSettings, other);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as TSettings);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Comparer.GetHashCode(this as TSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Text.RegularExpressions;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
@@ -26,9 +25,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
}
|
||||
}
|
||||
|
||||
public class DownloadStationSettings : IProviderConfig
|
||||
public class DownloadStationSettings : DownloadClientSettingsBase<DownloadStationSettings>
|
||||
{
|
||||
private static readonly DownloadStationSettingsValidator Validator = new DownloadStationSettingsValidator();
|
||||
private static readonly DownloadStationSettingsValidator Validator = new ();
|
||||
|
||||
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)]
|
||||
public string Host { get; set; }
|
||||
@@ -58,7 +57,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
this.Port = 5000;
|
||||
}
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Linq;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Download.Clients.Flood.Models;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Flood
|
||||
@@ -17,9 +16,9 @@ namespace NzbDrone.Core.Download.Clients.Flood
|
||||
}
|
||||
}
|
||||
|
||||
public class FloodSettings : IProviderConfig
|
||||
public class FloodSettings : DownloadClientSettingsBase<FloodSettings>
|
||||
{
|
||||
private static readonly FloodSettingsValidator Validator = new FloodSettingsValidator();
|
||||
private static readonly FloodSettingsValidator Validator = new ();
|
||||
|
||||
public FloodSettings()
|
||||
{
|
||||
@@ -69,7 +68,7 @@ namespace NzbDrone.Core.Download.Clients.Flood
|
||||
[FieldDefinition(10, Label = "DownloadClientFloodSettingsStartOnAdd", Type = FieldType.Checkbox)]
|
||||
public bool StartOnAdd { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using System.Text.RegularExpressions;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
using NzbDrone.Core.Validation.Paths;
|
||||
|
||||
@@ -34,9 +33,9 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
}
|
||||
}
|
||||
|
||||
public class FreeboxDownloadSettings : IProviderConfig
|
||||
public class FreeboxDownloadSettings : DownloadClientSettingsBase<FreeboxDownloadSettings>
|
||||
{
|
||||
private static readonly FreeboxDownloadSettingsValidator Validator = new FreeboxDownloadSettingsValidator();
|
||||
private static readonly FreeboxDownloadSettingsValidator Validator = new ();
|
||||
|
||||
public FreeboxDownloadSettings()
|
||||
{
|
||||
@@ -84,7 +83,7 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
[FieldDefinition(10, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox)]
|
||||
public bool AddPaused { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using FluentValidation;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Hadouken
|
||||
@@ -22,9 +21,9 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
|
||||
}
|
||||
}
|
||||
|
||||
public class HadoukenSettings : IProviderConfig
|
||||
public class HadoukenSettings : DownloadClientSettingsBase<HadoukenSettings>
|
||||
{
|
||||
private static readonly HadoukenSettingsValidator Validator = new HadoukenSettingsValidator();
|
||||
private static readonly HadoukenSettingsValidator Validator = new ();
|
||||
|
||||
public HadoukenSettings()
|
||||
{
|
||||
@@ -57,7 +56,7 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
|
||||
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategoryHelpText")]
|
||||
public string Category { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using FluentValidation;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
@@ -23,9 +22,9 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
}
|
||||
}
|
||||
|
||||
public class NzbVortexSettings : IProviderConfig
|
||||
public class NzbVortexSettings : DownloadClientSettingsBase<NzbVortexSettings>
|
||||
{
|
||||
private static readonly NzbVortexSettingsValidator Validator = new NzbVortexSettingsValidator();
|
||||
private static readonly NzbVortexSettingsValidator Validator = new ();
|
||||
|
||||
public NzbVortexSettings()
|
||||
{
|
||||
@@ -59,7 +58,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
[FieldDefinition(6, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "DownloadClientSettingsOlderPriorityEpisodeHelpText")]
|
||||
public int OlderTvPriority { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
@@ -21,9 +20,9 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
}
|
||||
}
|
||||
|
||||
public class NzbgetSettings : IProviderConfig
|
||||
public class NzbgetSettings : DownloadClientSettingsBase<NzbgetSettings>
|
||||
{
|
||||
private static readonly NzbgetSettingsValidator Validator = new NzbgetSettingsValidator();
|
||||
private static readonly NzbgetSettingsValidator Validator = new ();
|
||||
|
||||
public NzbgetSettings()
|
||||
{
|
||||
@@ -67,7 +66,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
[FieldDefinition(9, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox, HelpText = "DownloadClientNzbgetSettingsAddPausedHelpText")]
|
||||
public bool AddPaused { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using FluentValidation;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
using NzbDrone.Core.Validation.Paths;
|
||||
|
||||
@@ -15,9 +14,9 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
|
||||
}
|
||||
}
|
||||
|
||||
public class PneumaticSettings : IProviderConfig
|
||||
public class PneumaticSettings : DownloadClientSettingsBase<PneumaticSettings>
|
||||
{
|
||||
private static readonly PneumaticSettingsValidator Validator = new PneumaticSettingsValidator();
|
||||
private static readonly PneumaticSettingsValidator Validator = new ();
|
||||
|
||||
[FieldDefinition(0, Label = "DownloadClientPneumaticSettingsNzbFolder", Type = FieldType.Path, HelpText = "DownloadClientPneumaticSettingsNzbFolderHelpText")]
|
||||
public string NzbFolder { get; set; }
|
||||
@@ -25,7 +24,7 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
|
||||
[FieldDefinition(1, Label = "DownloadClientPneumaticSettingsStrmFolder", Type = FieldType.Path, HelpText = "DownloadClientPneumaticSettingsStrmFolderHelpText")]
|
||||
public string StrmFolder { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -239,7 +239,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
// Avoid removing torrents that haven't reached the global max ratio.
|
||||
// Removal also requires the torrent to be paused, in case a higher max ratio was set on the torrent itself (which is not exposed by the api).
|
||||
item.CanMoveFiles = item.CanBeRemoved = torrent.State == "pausedUP" && HasReachedSeedLimit(torrent, config);
|
||||
item.CanMoveFiles = item.CanBeRemoved = torrent.State is "pausedUP" or "stoppedUP" && HasReachedSeedLimit(torrent, config);
|
||||
|
||||
switch (torrent.State)
|
||||
{
|
||||
@@ -248,7 +248,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
item.Message = _localizationService.GetLocalizedString("DownloadClientQbittorrentTorrentStateError");
|
||||
break;
|
||||
|
||||
case "pausedDL": // torrent is paused and has NOT finished downloading
|
||||
case "stoppedDL": // torrent is stopped and has NOT finished downloading
|
||||
case "pausedDL": // torrent is paused and has NOT finished downloading (qBittorrent < 5)
|
||||
item.Status = DownloadItemStatus.Paused;
|
||||
break;
|
||||
|
||||
@@ -259,7 +260,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
item.Status = DownloadItemStatus.Queued;
|
||||
break;
|
||||
|
||||
case "pausedUP": // torrent is paused and has finished downloading:
|
||||
case "pausedUP": // torrent is paused and has finished downloading (qBittorent < 5)
|
||||
case "stoppedUP": // torrent is stopped and has finished downloading
|
||||
case "uploading": // torrent is being seeded and data is being transferred
|
||||
case "stalledUP": // torrent is being seeded, but no connection were made
|
||||
case "queuedUP": // queuing is enabled and torrent is queued for upload
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
@@ -19,9 +18,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
}
|
||||
}
|
||||
|
||||
public class QBittorrentSettings : IProviderConfig
|
||||
public class QBittorrentSettings : DownloadClientSettingsBase<QBittorrentSettings>
|
||||
{
|
||||
private static readonly QBittorrentSettingsValidator Validator = new QBittorrentSettingsValidator();
|
||||
private static readonly QBittorrentSettingsValidator Validator = new ();
|
||||
|
||||
public QBittorrentSettings()
|
||||
{
|
||||
@@ -74,7 +73,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
[FieldDefinition(13, Label = "DownloadClientQbittorrentSettingsContentLayout", Type = FieldType.Select, SelectOptions = typeof(QBittorrentContentLayout), HelpText = "DownloadClientQbittorrentSettingsContentLayoutHelpText")]
|
||||
public int ContentLayout { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using FluentValidation;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
@@ -32,9 +31,9 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
}
|
||||
}
|
||||
|
||||
public class SabnzbdSettings : IProviderConfig
|
||||
public class SabnzbdSettings : DownloadClientSettingsBase<SabnzbdSettings>
|
||||
{
|
||||
private static readonly SabnzbdSettingsValidator Validator = new SabnzbdSettingsValidator();
|
||||
private static readonly SabnzbdSettingsValidator Validator = new ();
|
||||
|
||||
public SabnzbdSettings()
|
||||
{
|
||||
@@ -78,7 +77,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
[FieldDefinition(9, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "DownloadClientSettingsOlderPriorityEpisodeHelpText")]
|
||||
public int OlderTvPriority { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients
|
||||
{
|
||||
public class TorrentSeedConfiguration
|
||||
{
|
||||
public static TorrentSeedConfiguration DefaultConfiguration = new TorrentSeedConfiguration();
|
||||
public static TorrentSeedConfiguration DefaultConfiguration = new ();
|
||||
|
||||
public double? Ratio { get; set; }
|
||||
public TimeSpan? SeedTime { get; set; }
|
||||
|
||||
@@ -2,7 +2,6 @@ using System.Text.RegularExpressions;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
@@ -24,9 +23,9 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
}
|
||||
}
|
||||
|
||||
public class TransmissionSettings : IProviderConfig
|
||||
public class TransmissionSettings : DownloadClientSettingsBase<TransmissionSettings>
|
||||
{
|
||||
private static readonly TransmissionSettingsValidator Validator = new TransmissionSettingsValidator();
|
||||
private static readonly TransmissionSettingsValidator Validator = new ();
|
||||
|
||||
public TransmissionSettings()
|
||||
{
|
||||
@@ -72,7 +71,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
[FieldDefinition(10, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox)]
|
||||
public bool AddPaused { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
public bool IsFinished { get; set; }
|
||||
public long Eta { get; set; }
|
||||
public TransmissionTorrentStatus Status { get; set; }
|
||||
public int SecondsDownloading { get; set; }
|
||||
public int SecondsSeeding { get; set; }
|
||||
public long SecondsDownloading { get; set; }
|
||||
public long SecondsSeeding { get; set; }
|
||||
public string ErrorString { get; set; }
|
||||
public long DownloadedEver { get; set; }
|
||||
public long UploadedEver { get; set; }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
@@ -17,9 +16,9 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
}
|
||||
}
|
||||
|
||||
public class RTorrentSettings : IProviderConfig
|
||||
public class RTorrentSettings : DownloadClientSettingsBase<RTorrentSettings>
|
||||
{
|
||||
private static readonly RTorrentSettingsValidator Validator = new RTorrentSettingsValidator();
|
||||
private static readonly RTorrentSettingsValidator Validator = new ();
|
||||
|
||||
public RTorrentSettings()
|
||||
{
|
||||
@@ -70,7 +69,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
[FieldDefinition(11, Label = "DownloadClientRTorrentSettingsAddStopped", Type = FieldType.Checkbox, HelpText = "DownloadClientRTorrentSettingsAddStoppedHelpText")]
|
||||
public bool AddStopped { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
@@ -17,9 +16,9 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
}
|
||||
}
|
||||
|
||||
public class UTorrentSettings : IProviderConfig
|
||||
public class UTorrentSettings : DownloadClientSettingsBase<UTorrentSettings>
|
||||
{
|
||||
private static readonly UTorrentSettingsValidator Validator = new UTorrentSettingsValidator();
|
||||
private static readonly UTorrentSettingsValidator Validator = new ();
|
||||
|
||||
public UTorrentSettings()
|
||||
{
|
||||
@@ -65,7 +64,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
[FieldToken(TokenField.HelpText, "DownloadClientSettingsInitialState", "clientName", "uTorrent")]
|
||||
public int IntialState { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -1,14 +1,35 @@
|
||||
using NzbDrone.Core.Indexers;
|
||||
using System;
|
||||
using Equ;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
namespace NzbDrone.Core.Download
|
||||
{
|
||||
public class DownloadClientDefinition : ProviderDefinition
|
||||
public class DownloadClientDefinition : ProviderDefinition, IEquatable<DownloadClientDefinition>
|
||||
{
|
||||
private static readonly MemberwiseEqualityComparer<DownloadClientDefinition> Comparer = MemberwiseEqualityComparer<DownloadClientDefinition>.ByProperties;
|
||||
|
||||
[MemberwiseEqualityIgnore]
|
||||
public DownloadProtocol Protocol { get; set; }
|
||||
|
||||
public int Priority { get; set; } = 1;
|
||||
|
||||
public bool RemoveCompletedDownloads { get; set; } = true;
|
||||
public bool RemoveFailedDownloads { get; set; } = true;
|
||||
|
||||
public bool Equals(DownloadClientDefinition other)
|
||||
{
|
||||
return Comparer.Equals(this, other);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as DownloadClientDefinition);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Comparer.GetHashCode(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
|
||||
|
||||
if (series.Tags.Any())
|
||||
{
|
||||
var tags = _tagRepo.Get(series.Tags);
|
||||
var tags = _tagRepo.GetTags(series.Tags);
|
||||
|
||||
foreach (var tag in tags)
|
||||
{
|
||||
|
||||
@@ -29,18 +29,17 @@ namespace NzbDrone.Core.ImportLists.AniList
|
||||
}
|
||||
}
|
||||
|
||||
public class AniListSettingsBase<TSettings> : IImportListSettings
|
||||
public class AniListSettingsBase<TSettings> : ImportListSettingsBase<TSettings>
|
||||
where TSettings : AniListSettingsBase<TSettings>
|
||||
{
|
||||
protected virtual AbstractValidator<TSettings> Validator => new AniListSettingsBaseValidator<TSettings>();
|
||||
private static readonly AniListSettingsBaseValidator<TSettings> Validator = new ();
|
||||
|
||||
public AniListSettingsBase()
|
||||
{
|
||||
BaseUrl = "https://graphql.anilist.co";
|
||||
SignIn = "startOAuth";
|
||||
}
|
||||
|
||||
public string BaseUrl { get; set; }
|
||||
public override string BaseUrl { get; set; } = "https://graphql.anilist.co";
|
||||
|
||||
[FieldDefinition(0, Label = "ImportListsSettingsAccessToken", Type = FieldType.Textbox, Hidden = HiddenType.Hidden)]
|
||||
public string AccessToken { get; set; }
|
||||
@@ -54,7 +53,7 @@ namespace NzbDrone.Core.ImportLists.AniList
|
||||
[FieldDefinition(99, Label = "ImportListsAniListSettingsAuthenticateWithAniList", Type = FieldType.OAuth)]
|
||||
public string SignIn { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate((TSettings)this));
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists.AniList.List
|
||||
{
|
||||
public class AniListSettingsValidator : AniListSettingsBaseValidator<AniListSettings>
|
||||
{
|
||||
public AniListSettingsValidator()
|
||||
: base()
|
||||
{
|
||||
RuleFor(c => c.Username).NotEmpty();
|
||||
|
||||
@@ -18,10 +18,11 @@ namespace NzbDrone.Core.ImportLists.AniList.List
|
||||
|
||||
public class AniListSettings : AniListSettingsBase<AniListSettings>
|
||||
{
|
||||
public const string sectionImport = "Import List Status";
|
||||
public const string SectionImport = "Import List Status";
|
||||
|
||||
private static readonly AniListSettingsValidator Validator = new ();
|
||||
|
||||
public AniListSettings()
|
||||
: base()
|
||||
{
|
||||
ImportCurrent = true;
|
||||
ImportPlanning = true;
|
||||
@@ -29,42 +30,45 @@ namespace NzbDrone.Core.ImportLists.AniList.List
|
||||
ImportFinished = true;
|
||||
}
|
||||
|
||||
protected override AbstractValidator<AniListSettings> Validator => new AniListSettingsValidator();
|
||||
|
||||
[FieldDefinition(1, Label = "Username", HelpText = "ImportListsAniListSettingsUsernameHelpText")]
|
||||
public string Username { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "ImportListsAniListSettingsImportWatching", Type = FieldType.Checkbox, Section = sectionImport, HelpText = "ImportListsAniListSettingsImportWatchingHelpText")]
|
||||
[FieldDefinition(2, Label = "ImportListsAniListSettingsImportWatching", Type = FieldType.Checkbox, Section = SectionImport, HelpText = "ImportListsAniListSettingsImportWatchingHelpText")]
|
||||
public bool ImportCurrent { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "ImportListsAniListSettingsImportPlanning", Type = FieldType.Checkbox, Section = sectionImport, HelpText = "ImportListsAniListSettingsImportPlanningHelpText")]
|
||||
[FieldDefinition(3, Label = "ImportListsAniListSettingsImportPlanning", Type = FieldType.Checkbox, Section = SectionImport, HelpText = "ImportListsAniListSettingsImportPlanningHelpText")]
|
||||
public bool ImportPlanning { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "ImportListsAniListSettingsImportCompleted", Type = FieldType.Checkbox, Section = sectionImport, HelpText = "ImportListsAniListSettingsImportCompletedHelpText")]
|
||||
[FieldDefinition(4, Label = "ImportListsAniListSettingsImportCompleted", Type = FieldType.Checkbox, Section = SectionImport, HelpText = "ImportListsAniListSettingsImportCompletedHelpText")]
|
||||
public bool ImportCompleted { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "ImportListsAniListSettingsImportDropped", Type = FieldType.Checkbox, Section = sectionImport, HelpText = "ImportListsAniListSettingsImportDroppedHelpText")]
|
||||
[FieldDefinition(5, Label = "ImportListsAniListSettingsImportDropped", Type = FieldType.Checkbox, Section = SectionImport, HelpText = "ImportListsAniListSettingsImportDroppedHelpText")]
|
||||
public bool ImportDropped { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "ImportListsAniListSettingsImportPaused", Type = FieldType.Checkbox, Section = sectionImport, HelpText = "ImportListsAniListSettingsImportPausedHelpText")]
|
||||
[FieldDefinition(6, Label = "ImportListsAniListSettingsImportPaused", Type = FieldType.Checkbox, Section = SectionImport, HelpText = "ImportListsAniListSettingsImportPausedHelpText")]
|
||||
public bool ImportPaused { get; set; }
|
||||
|
||||
[FieldDefinition(7, Label = "ImportListsAniListSettingsImportRepeating", Type = FieldType.Checkbox, Section = sectionImport, HelpText = "ImportListsAniListSettingsImportRepeatingHelpText")]
|
||||
[FieldDefinition(7, Label = "ImportListsAniListSettingsImportRepeating", Type = FieldType.Checkbox, Section = SectionImport, HelpText = "ImportListsAniListSettingsImportRepeatingHelpText")]
|
||||
public bool ImportRepeating { get; set; }
|
||||
|
||||
[FieldDefinition(8, Label = "ImportListsAniListSettingsImportFinished", Type = FieldType.Checkbox, Section = sectionImport, HelpText = "ImportListsAniListSettingsImportFinishedHelpText")]
|
||||
[FieldDefinition(8, Label = "ImportListsAniListSettingsImportFinished", Type = FieldType.Checkbox, Section = SectionImport, HelpText = "ImportListsAniListSettingsImportFinishedHelpText")]
|
||||
public bool ImportFinished { get; set; }
|
||||
|
||||
[FieldDefinition(9, Label = "ImportListsAniListSettingsImportReleasing", Type = FieldType.Checkbox, Section = sectionImport, HelpText = "ImportListsAniListSettingsImportReleasingHelpText")]
|
||||
[FieldDefinition(9, Label = "ImportListsAniListSettingsImportReleasing", Type = FieldType.Checkbox, Section = SectionImport, HelpText = "ImportListsAniListSettingsImportReleasingHelpText")]
|
||||
public bool ImportReleasing { get; set; }
|
||||
|
||||
[FieldDefinition(10, Label = "ImportListsAniListSettingsImportNotYetReleased", Type = FieldType.Checkbox, Section = sectionImport, HelpText = "ImportListsAniListSettingsImportNotYetReleasedHelpText")]
|
||||
[FieldDefinition(10, Label = "ImportListsAniListSettingsImportNotYetReleased", Type = FieldType.Checkbox, Section = SectionImport, HelpText = "ImportListsAniListSettingsImportNotYetReleasedHelpText")]
|
||||
public bool ImportUnreleased { get; set; }
|
||||
|
||||
[FieldDefinition(11, Label = "ImportListsAniListSettingsImportCancelled", Type = FieldType.Checkbox, Section = sectionImport, HelpText = "ImportListsAniListSettingsImportCancelledHelpText")]
|
||||
[FieldDefinition(11, Label = "ImportListsAniListSettingsImportCancelled", Type = FieldType.Checkbox, Section = SectionImport, HelpText = "ImportListsAniListSettingsImportCancelledHelpText")]
|
||||
public bool ImportCancelled { get; set; }
|
||||
|
||||
[FieldDefinition(12, Label = "ImportListsAniListSettingsImportHiatus", Type = FieldType.Checkbox, Section = sectionImport, HelpText = "ImportListsAniListSettingsImportHiatusHelpText")]
|
||||
[FieldDefinition(12, Label = "ImportListsAniListSettingsImportHiatus", Type = FieldType.Checkbox, Section = SectionImport, HelpText = "ImportListsAniListSettingsImportHiatusHelpText")]
|
||||
public bool ImportHiatus { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,19 +13,14 @@ namespace NzbDrone.Core.ImportLists.Custom
|
||||
}
|
||||
}
|
||||
|
||||
public class CustomSettings : IImportListSettings
|
||||
public class CustomSettings : ImportListSettingsBase<CustomSettings>
|
||||
{
|
||||
private static readonly CustomSettingsValidator Validator = new CustomSettingsValidator();
|
||||
|
||||
public CustomSettings()
|
||||
{
|
||||
BaseUrl = "";
|
||||
}
|
||||
private static readonly CustomSettingsValidator Validator = new ();
|
||||
|
||||
[FieldDefinition(0, Label = "ImportListsCustomListSettingsUrl", HelpText = "ImportListsCustomListSettingsUrlHelpText")]
|
||||
public string BaseUrl { get; set; }
|
||||
public override string BaseUrl { get; set; } = string.Empty;
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -14,16 +14,16 @@ namespace NzbDrone.Core.ImportLists.Imdb
|
||||
}
|
||||
}
|
||||
|
||||
public class ImdbListSettings : IImportListSettings
|
||||
public class ImdbListSettings : ImportListSettingsBase<ImdbListSettings>
|
||||
{
|
||||
private static readonly ImdbSettingsValidator Validator = new ImdbSettingsValidator();
|
||||
private static readonly ImdbSettingsValidator Validator = new ();
|
||||
|
||||
public string BaseUrl { get; set; }
|
||||
public override string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "ImportListsImdbSettingsListId", HelpText = "ImportListsImdbSettingsListIdHelpText")]
|
||||
public string ListId { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
using System;
|
||||
using Equ;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists
|
||||
{
|
||||
public class ImportListDefinition : ProviderDefinition
|
||||
public class ImportListDefinition : ProviderDefinition, IEquatable<ImportListDefinition>
|
||||
{
|
||||
private static readonly MemberwiseEqualityComparer<ImportListDefinition> Comparer = MemberwiseEqualityComparer<ImportListDefinition>.ByProperties;
|
||||
|
||||
public bool EnableAutomaticAdd { get; set; }
|
||||
public bool SearchForMissingEpisodes { get; set; }
|
||||
public MonitorTypes ShouldMonitor { get; set; }
|
||||
@@ -15,10 +18,31 @@ namespace NzbDrone.Core.ImportLists
|
||||
public bool SeasonFolder { get; set; }
|
||||
public string RootFolderPath { get; set; }
|
||||
|
||||
[MemberwiseEqualityIgnore]
|
||||
public override bool Enable => EnableAutomaticAdd;
|
||||
|
||||
[MemberwiseEqualityIgnore]
|
||||
public ImportListStatus Status { get; set; }
|
||||
|
||||
[MemberwiseEqualityIgnore]
|
||||
public ImportListType ListType { get; set; }
|
||||
|
||||
[MemberwiseEqualityIgnore]
|
||||
public TimeSpan MinRefreshInterval { get; set; }
|
||||
|
||||
public bool Equals(ImportListDefinition other)
|
||||
{
|
||||
return Comparer.Equals(this, other);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as ImportListDefinition);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Comparer.GetHashCode(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
31
src/NzbDrone.Core/ImportLists/ImportListSettingsBase.cs
Normal file
31
src/NzbDrone.Core/ImportLists/ImportListSettingsBase.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using Equ;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists
|
||||
{
|
||||
public abstract class ImportListSettingsBase<TSettings> : IImportListSettings, IEquatable<TSettings>
|
||||
where TSettings : ImportListSettingsBase<TSettings>
|
||||
{
|
||||
private static readonly MemberwiseEqualityComparer<TSettings> Comparer = MemberwiseEqualityComparer<TSettings>.ByProperties;
|
||||
|
||||
public abstract string BaseUrl { get; set; }
|
||||
|
||||
public abstract NzbDroneValidationResult Validate();
|
||||
|
||||
public bool Equals(TSettings other)
|
||||
{
|
||||
return Comparer.Equals(this as TSettings, other);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as TSettings);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Comparer.GetHashCode(this as TSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,16 +24,11 @@ namespace NzbDrone.Core.ImportLists.MyAnimeList
|
||||
}
|
||||
}
|
||||
|
||||
public class MyAnimeListSettings : IImportListSettings
|
||||
public class MyAnimeListSettings : ImportListSettingsBase<MyAnimeListSettings>
|
||||
{
|
||||
public string BaseUrl { get; set; }
|
||||
private static readonly MalSettingsValidator Validator = new ();
|
||||
|
||||
protected AbstractValidator<MyAnimeListSettings> Validator => new MalSettingsValidator();
|
||||
|
||||
public MyAnimeListSettings()
|
||||
{
|
||||
BaseUrl = "https://api.myanimelist.net/v2";
|
||||
}
|
||||
public override string BaseUrl { get; set; } = "https://api.myanimelist.net/v2";
|
||||
|
||||
[FieldDefinition(0, Label = "ImportListsMyAnimeListSettingsListStatus", Type = FieldType.Select, SelectOptions = typeof(MyAnimeListStatus), HelpText = "ImportListsMyAnimeListSettingsListStatusHelpText")]
|
||||
public int ListStatus { get; set; }
|
||||
@@ -50,7 +45,7 @@ namespace NzbDrone.Core.ImportLists.MyAnimeList
|
||||
[FieldDefinition(99, Label = "ImportListsMyAnimeListSettingsAuthenticateWithMyAnimeList", Type = FieldType.OAuth)]
|
||||
public string SignIn { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ namespace NzbDrone.Core.ImportLists.Plex
|
||||
}
|
||||
}
|
||||
|
||||
public class PlexListSettings : IImportListSettings
|
||||
public class PlexListSettings : ImportListSettingsBase<PlexListSettings>
|
||||
{
|
||||
protected virtual PlexListSettingsValidator Validator => new PlexListSettingsValidator();
|
||||
private static readonly PlexListSettingsValidator Validator = new ();
|
||||
|
||||
public PlexListSettings()
|
||||
{
|
||||
@@ -25,7 +25,7 @@ namespace NzbDrone.Core.ImportLists.Plex
|
||||
|
||||
public virtual string Scope => "";
|
||||
|
||||
public string BaseUrl { get; set; }
|
||||
public override string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(0, Label = "ImportListsSettingsAccessToken", Type = FieldType.Textbox, Hidden = HiddenType.Hidden)]
|
||||
public string AccessToken { get; set; }
|
||||
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.ImportLists.Plex
|
||||
[FieldDefinition(99, Label = "ImportListsPlexSettingsAuthenticateWithPlex", Type = FieldType.OAuth)]
|
||||
public string SignIn { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ namespace NzbDrone.Core.ImportLists.Rss.Plex
|
||||
}
|
||||
}
|
||||
|
||||
public class PlexRssImportSettings : RssImportBaseSettings
|
||||
public class PlexRssImportSettings : RssImportBaseSettings<PlexRssImportSettings>
|
||||
{
|
||||
private PlexRssImportSettingsValidator Validator => new ();
|
||||
private static readonly PlexRssImportSettingsValidator Validator = new ();
|
||||
|
||||
[FieldDefinition(0, Label = "ImportListsSettingsRssUrl", Type = FieldType.Textbox, HelpLink = "https://app.plex.tv/desktop/#!/settings/watchlist")]
|
||||
public override string Url { get; set; }
|
||||
|
||||
@@ -8,7 +8,7 @@ using NzbDrone.Core.Parser;
|
||||
namespace NzbDrone.Core.ImportLists.Rss
|
||||
{
|
||||
public class RssImportBase<TSettings> : HttpImportListBase<TSettings>
|
||||
where TSettings : RssImportBaseSettings, new()
|
||||
where TSettings : RssImportBaseSettings<TSettings>, new()
|
||||
{
|
||||
public override string Name => "RSS List Base";
|
||||
public override ImportListType ListType => ImportListType.Advanced;
|
||||
@@ -36,7 +36,7 @@ namespace NzbDrone.Core.ImportLists.Rss
|
||||
|
||||
public override IImportListRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new RssImportRequestGenerator
|
||||
return new RssImportRequestGenerator<TSettings>
|
||||
{
|
||||
Settings = Settings
|
||||
};
|
||||
|
||||
@@ -4,7 +4,8 @@ using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists.Rss
|
||||
{
|
||||
public class RssImportSettingsValidator : AbstractValidator<RssImportBaseSettings>
|
||||
public class RssImportSettingsValidator<TSettings> : AbstractValidator<TSettings>
|
||||
where TSettings : RssImportBaseSettings<TSettings>
|
||||
{
|
||||
public RssImportSettingsValidator()
|
||||
{
|
||||
@@ -12,18 +13,19 @@ namespace NzbDrone.Core.ImportLists.Rss
|
||||
}
|
||||
}
|
||||
|
||||
public class RssImportBaseSettings : IImportListSettings
|
||||
public class RssImportBaseSettings<TSettings> : ImportListSettingsBase<TSettings>
|
||||
where TSettings : RssImportBaseSettings<TSettings>
|
||||
{
|
||||
private RssImportSettingsValidator Validator => new ();
|
||||
private static readonly RssImportSettingsValidator<TSettings> Validator = new ();
|
||||
|
||||
public string BaseUrl { get; set; }
|
||||
public override string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(0, Label = "ImportListsSettingsRssUrl", Type = FieldType.Textbox)]
|
||||
public virtual string Url { get; set; }
|
||||
|
||||
public virtual NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
return new NzbDroneValidationResult(Validator.Validate(this as TSettings));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,10 @@ using NzbDrone.Common.Http;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists.Rss
|
||||
{
|
||||
public class RssImportRequestGenerator : IImportListRequestGenerator
|
||||
public class RssImportRequestGenerator<TSettings> : IImportListRequestGenerator
|
||||
where TSettings : RssImportBaseSettings<TSettings>, new()
|
||||
{
|
||||
public RssImportBaseSettings Settings { get; set; }
|
||||
public RssImportBaseSettings<TSettings> Settings { get; set; }
|
||||
|
||||
public virtual ImportListPageableRequestChain GetListItems()
|
||||
{
|
||||
@@ -18,9 +19,7 @@ namespace NzbDrone.Core.ImportLists.Rss
|
||||
|
||||
private IEnumerable<ImportListRequest> GetSeriesRequest()
|
||||
{
|
||||
var request = new ImportListRequest(Settings.Url, HttpAccept.Rss);
|
||||
|
||||
yield return request;
|
||||
yield return new ImportListRequest(Settings.Url, HttpAccept.Rss);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,18 +24,17 @@ namespace NzbDrone.Core.ImportLists.Simkl
|
||||
}
|
||||
}
|
||||
|
||||
public class SimklSettingsBase<TSettings> : IImportListSettings
|
||||
public class SimklSettingsBase<TSettings> : ImportListSettingsBase<TSettings>
|
||||
where TSettings : SimklSettingsBase<TSettings>
|
||||
{
|
||||
protected virtual AbstractValidator<TSettings> Validator => new SimklSettingsBaseValidator<TSettings>();
|
||||
private static readonly SimklSettingsBaseValidator<TSettings> Validator = new ();
|
||||
|
||||
public SimklSettingsBase()
|
||||
{
|
||||
BaseUrl = "https://api.simkl.com";
|
||||
SignIn = "startOAuth";
|
||||
}
|
||||
|
||||
public string BaseUrl { get; set; }
|
||||
public override string BaseUrl { get; set; } = "https://api.simkl.com";
|
||||
|
||||
[FieldDefinition(0, Label = "ImportListsSettingsAccessToken", Type = FieldType.Textbox, Hidden = HiddenType.Hidden)]
|
||||
public string AccessToken { get; set; }
|
||||
@@ -52,7 +51,7 @@ namespace NzbDrone.Core.ImportLists.Simkl
|
||||
[FieldDefinition(99, Label = "ImportListsSimklSettingsAuthenticatewithSimkl", Type = FieldType.OAuth)]
|
||||
public string SignIn { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate((TSettings)this));
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists.Simkl.User
|
||||
{
|
||||
public class SimklUserSettingsValidator : SimklSettingsBaseValidator<SimklUserSettings>
|
||||
{
|
||||
public SimklUserSettingsValidator()
|
||||
: base()
|
||||
{
|
||||
RuleFor(c => c.ListType).NotNull();
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace NzbDrone.Core.ImportLists.Simkl.User
|
||||
|
||||
public class SimklUserSettings : SimklSettingsBase<SimklUserSettings>
|
||||
{
|
||||
protected override AbstractValidator<SimklUserSettings> Validator => new SimklUserSettingsValidator();
|
||||
private static readonly SimklUserSettingsValidator Validator = new ();
|
||||
|
||||
public SimklUserSettings()
|
||||
{
|
||||
@@ -27,5 +27,10 @@ namespace NzbDrone.Core.ImportLists.Simkl.User
|
||||
|
||||
[FieldDefinition(1, Label = "ImportListsSimklSettingsShowType", Type = FieldType.Select, SelectOptions = typeof(SimklUserShowType), HelpText = "ImportListsSimklSettingsShowTypeHelpText")]
|
||||
public int ShowType { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,12 @@ namespace NzbDrone.Core.ImportLists.Sonarr
|
||||
}
|
||||
}
|
||||
|
||||
public class SonarrSettings : IImportListSettings
|
||||
public class SonarrSettings : ImportListSettingsBase<SonarrSettings>
|
||||
{
|
||||
private static readonly SonarrSettingsValidator Validator = new SonarrSettingsValidator();
|
||||
private static readonly SonarrSettingsValidator Validator = new ();
|
||||
|
||||
public SonarrSettings()
|
||||
{
|
||||
BaseUrl = "";
|
||||
ApiKey = "";
|
||||
ProfileIds = Array.Empty<int>();
|
||||
LanguageProfileIds = Array.Empty<int>();
|
||||
@@ -30,7 +29,7 @@ namespace NzbDrone.Core.ImportLists.Sonarr
|
||||
}
|
||||
|
||||
[FieldDefinition(0, Label = "ImportListsSonarrSettingsFullUrl", HelpText = "ImportListsSonarrSettingsFullUrlHelpText")]
|
||||
public string BaseUrl { get; set; }
|
||||
public override string BaseUrl { get; set; } = string.Empty;
|
||||
|
||||
[FieldDefinition(1, Label = "ApiKey", HelpText = "ImportListsSonarrSettingsApiKeyHelpText")]
|
||||
public string ApiKey { get; set; }
|
||||
@@ -51,7 +50,7 @@ namespace NzbDrone.Core.ImportLists.Sonarr
|
||||
[FieldDefinition(6, Type = FieldType.Select, SelectOptionsProviderAction = "getLanguageProfiles", Label = "Language Profiles", HelpText = "Language Profiles from the source instance to import from")]
|
||||
public IEnumerable<int> LanguageProfileIds { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists.Trakt.List
|
||||
{
|
||||
public class TraktListSettingsValidator : TraktSettingsBaseValidator<TraktListSettings>
|
||||
{
|
||||
public TraktListSettingsValidator()
|
||||
: base()
|
||||
{
|
||||
RuleFor(c => c.Username).NotEmpty();
|
||||
RuleFor(c => c.Listname).NotEmpty();
|
||||
@@ -15,12 +15,17 @@ namespace NzbDrone.Core.ImportLists.Trakt.List
|
||||
|
||||
public class TraktListSettings : TraktSettingsBase<TraktListSettings>
|
||||
{
|
||||
protected override AbstractValidator<TraktListSettings> Validator => new TraktListSettingsValidator();
|
||||
private static readonly TraktListSettingsValidator Validator = new ();
|
||||
|
||||
[FieldDefinition(1, Label = "Username", HelpText = "ImportListsTraktSettingsUsernameHelpText")]
|
||||
public string Username { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "ImportListsTraktSettingsListName", HelpText = "ImportListsTraktSettingsListNameHelpText")]
|
||||
public string Listname { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@ using System.Text.RegularExpressions;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists.Trakt.Popular
|
||||
{
|
||||
public class TraktPopularSettingsValidator : TraktSettingsBaseValidator<TraktPopularSettings>
|
||||
{
|
||||
public TraktPopularSettingsValidator()
|
||||
: base()
|
||||
{
|
||||
RuleFor(c => c.TraktListType).NotNull();
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.ImportLists.Trakt.Popular
|
||||
|
||||
public class TraktPopularSettings : TraktSettingsBase<TraktPopularSettings>
|
||||
{
|
||||
protected override AbstractValidator<TraktPopularSettings> Validator => new TraktPopularSettingsValidator();
|
||||
private static readonly TraktPopularSettingsValidator Validator = new ();
|
||||
|
||||
public TraktPopularSettings()
|
||||
{
|
||||
@@ -46,5 +46,10 @@ namespace NzbDrone.Core.ImportLists.Trakt.Popular
|
||||
|
||||
[FieldDefinition(5, Label = "ImportListsTraktSettingsYears", HelpText = "ImportListsTraktSettingsYearsHelpText")]
|
||||
public string Years { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,19 +34,18 @@ namespace NzbDrone.Core.ImportLists.Trakt
|
||||
}
|
||||
}
|
||||
|
||||
public class TraktSettingsBase<TSettings> : IImportListSettings
|
||||
public class TraktSettingsBase<TSettings> : ImportListSettingsBase<TSettings>
|
||||
where TSettings : TraktSettingsBase<TSettings>
|
||||
{
|
||||
protected virtual AbstractValidator<TSettings> Validator => new TraktSettingsBaseValidator<TSettings>();
|
||||
private static readonly TraktSettingsBaseValidator<TSettings> Validator = new ();
|
||||
|
||||
public TraktSettingsBase()
|
||||
{
|
||||
BaseUrl = "https://api.trakt.tv";
|
||||
SignIn = "startOAuth";
|
||||
Limit = 100;
|
||||
}
|
||||
|
||||
public string BaseUrl { get; set; }
|
||||
public override string BaseUrl { get; set; } = "https://api.trakt.tv";
|
||||
|
||||
[FieldDefinition(0, Label = "ImportListsSettingsAccessToken", Type = FieldType.Textbox, Hidden = HiddenType.Hidden)]
|
||||
public string AccessToken { get; set; }
|
||||
@@ -69,7 +68,7 @@ namespace NzbDrone.Core.ImportLists.Trakt
|
||||
[FieldDefinition(99, Label = "ImportListsTraktSettingsAuthenticateWithTrakt", Type = FieldType.OAuth)]
|
||||
public string SignIn { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate((TSettings)this));
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists.Trakt.User
|
||||
{
|
||||
public class TraktUserSettingsValidator : TraktSettingsBaseValidator<TraktUserSettings>
|
||||
{
|
||||
public TraktUserSettingsValidator()
|
||||
: base()
|
||||
{
|
||||
RuleFor(c => c.TraktListType).NotNull();
|
||||
RuleFor(c => c.TraktWatchedListType).NotNull();
|
||||
@@ -16,7 +16,7 @@ namespace NzbDrone.Core.ImportLists.Trakt.User
|
||||
|
||||
public class TraktUserSettings : TraktSettingsBase<TraktUserSettings>
|
||||
{
|
||||
protected override AbstractValidator<TraktUserSettings> Validator => new TraktUserSettingsValidator();
|
||||
private static readonly TraktUserSettingsValidator Validator = new ();
|
||||
|
||||
public TraktUserSettings()
|
||||
{
|
||||
@@ -36,6 +36,11 @@ namespace NzbDrone.Core.ImportLists.Trakt.User
|
||||
|
||||
[FieldDefinition(4, Label = "Username", HelpText = "ImportListsTraktSettingsUserListUsernameHelpText")]
|
||||
public string Username { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
|
||||
public enum TraktUserWatchSorting
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Equ;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Languages;
|
||||
@@ -18,7 +19,7 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
|
||||
}
|
||||
}
|
||||
|
||||
public class BroadcastheNetSettings : ITorrentIndexerSettings
|
||||
public class BroadcastheNetSettings : PropertywiseEquatable<BroadcastheNetSettings>, ITorrentIndexerSettings
|
||||
{
|
||||
private static readonly BroadcastheNetSettingsValidator Validator = new ();
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Equ;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Languages;
|
||||
@@ -15,9 +16,9 @@ namespace NzbDrone.Core.Indexers.Fanzub
|
||||
}
|
||||
}
|
||||
|
||||
public class FanzubSettings : IIndexerSettings
|
||||
public class FanzubSettings : PropertywiseEquatable<FanzubSettings>, IIndexerSettings
|
||||
{
|
||||
private static readonly FanzubSettingsValidator Validator = new FanzubSettingsValidator();
|
||||
private static readonly FanzubSettingsValidator Validator = new ();
|
||||
|
||||
public FanzubSettings()
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Equ;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Languages;
|
||||
@@ -19,9 +20,9 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||
}
|
||||
}
|
||||
|
||||
public class FileListSettings : ITorrentIndexerSettings
|
||||
public class FileListSettings : PropertywiseEquatable<FileListSettings>, ITorrentIndexerSettings
|
||||
{
|
||||
private static readonly FileListSettingsValidator Validator = new FileListSettingsValidator();
|
||||
private static readonly FileListSettingsValidator Validator = new ();
|
||||
|
||||
public FileListSettings()
|
||||
{
|
||||
@@ -61,7 +62,7 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||
public int MinimumSeeders { get; set; }
|
||||
|
||||
[FieldDefinition(7)]
|
||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
|
||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new ();
|
||||
|
||||
[FieldDefinition(8, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
|
||||
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Equ;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Languages;
|
||||
@@ -18,7 +19,7 @@ namespace NzbDrone.Core.Indexers.HDBits
|
||||
}
|
||||
}
|
||||
|
||||
public class HDBitsSettings : ITorrentIndexerSettings
|
||||
public class HDBitsSettings : PropertywiseEquatable<HDBitsSettings>, ITorrentIndexerSettings
|
||||
{
|
||||
private static readonly HDBitsSettingsValidator Validator = new ();
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using Equ;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
@@ -25,9 +26,9 @@ namespace NzbDrone.Core.Indexers.IPTorrents
|
||||
}
|
||||
}
|
||||
|
||||
public class IPTorrentsSettings : ITorrentIndexerSettings
|
||||
public class IPTorrentsSettings : PropertywiseEquatable<IPTorrentsSettings>, ITorrentIndexerSettings
|
||||
{
|
||||
private static readonly IPTorrentsSettingsValidator Validator = new IPTorrentsSettingsValidator();
|
||||
private static readonly IPTorrentsSettingsValidator Validator = new ();
|
||||
|
||||
public IPTorrentsSettings()
|
||||
{
|
||||
@@ -42,7 +43,7 @@ namespace NzbDrone.Core.Indexers.IPTorrents
|
||||
public int MinimumSeeders { get; set; }
|
||||
|
||||
[FieldDefinition(2)]
|
||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
|
||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new ();
|
||||
|
||||
[FieldDefinition(3, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
|
||||
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
using System;
|
||||
using Equ;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
public class IndexerDefinition : ProviderDefinition
|
||||
public class IndexerDefinition : ProviderDefinition, IEquatable<IndexerDefinition>
|
||||
{
|
||||
private static readonly MemberwiseEqualityComparer<IndexerDefinition> Comparer = MemberwiseEqualityComparer<IndexerDefinition>.ByProperties;
|
||||
|
||||
public const int DefaultPriority = 25;
|
||||
|
||||
public IndexerDefinition()
|
||||
@@ -11,18 +15,41 @@ namespace NzbDrone.Core.Indexers
|
||||
Priority = DefaultPriority;
|
||||
}
|
||||
|
||||
[MemberwiseEqualityIgnore]
|
||||
public DownloadProtocol Protocol { get; set; }
|
||||
|
||||
[MemberwiseEqualityIgnore]
|
||||
public bool SupportsRss { get; set; }
|
||||
|
||||
[MemberwiseEqualityIgnore]
|
||||
public bool SupportsSearch { get; set; }
|
||||
|
||||
public bool EnableRss { get; set; }
|
||||
public bool EnableAutomaticSearch { get; set; }
|
||||
public bool EnableInteractiveSearch { get; set; }
|
||||
public int DownloadClientId { get; set; }
|
||||
public DownloadProtocol Protocol { get; set; }
|
||||
public bool SupportsRss { get; set; }
|
||||
public bool SupportsSearch { get; set; }
|
||||
public int Priority { get; set; }
|
||||
public int SeasonSearchMaximumSingleEpisodeAge { get; set; }
|
||||
|
||||
[MemberwiseEqualityIgnore]
|
||||
public override bool Enable => EnableRss || EnableAutomaticSearch || EnableInteractiveSearch;
|
||||
|
||||
[MemberwiseEqualityIgnore]
|
||||
public IndexerStatus Status { get; set; }
|
||||
|
||||
public bool Equals(IndexerDefinition other)
|
||||
{
|
||||
return Comparer.Equals(this, other);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as IndexerDefinition);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Comparer.GetHashCode(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Equ;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
@@ -28,7 +29,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
return settings.BaseUrl != null && ApiKeyWhiteList.Any(c => settings.BaseUrl.ToLowerInvariant().Contains(c));
|
||||
}
|
||||
|
||||
private static readonly Regex AdditionalParametersRegex = new Regex(@"(&.+?\=.+?)+", RegexOptions.Compiled);
|
||||
private static readonly Regex AdditionalParametersRegex = new (@"(&.+?\=.+?)+", RegexOptions.Compiled);
|
||||
|
||||
public NewznabSettingsValidator()
|
||||
{
|
||||
@@ -48,9 +49,9 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
}
|
||||
}
|
||||
|
||||
public class NewznabSettings : IIndexerSettings
|
||||
public class NewznabSettings : PropertywiseEquatable<NewznabSettings>, IIndexerSettings
|
||||
{
|
||||
private static readonly NewznabSettingsValidator Validator = new NewznabSettingsValidator();
|
||||
private static readonly NewznabSettingsValidator Validator = new ();
|
||||
|
||||
public NewznabSettings()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using Equ;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Languages;
|
||||
@@ -19,9 +20,9 @@ namespace NzbDrone.Core.Indexers.Nyaa
|
||||
}
|
||||
}
|
||||
|
||||
public class NyaaSettings : ITorrentIndexerSettings
|
||||
public class NyaaSettings : PropertywiseEquatable<NyaaSettings>, ITorrentIndexerSettings
|
||||
{
|
||||
private static readonly NyaaSettingsValidator Validator = new NyaaSettingsValidator();
|
||||
private static readonly NyaaSettingsValidator Validator = new ();
|
||||
|
||||
public NyaaSettings()
|
||||
{
|
||||
@@ -44,7 +45,7 @@ namespace NzbDrone.Core.Indexers.Nyaa
|
||||
public int MinimumSeeders { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
|
||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new ();
|
||||
|
||||
[FieldDefinition(5, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
|
||||
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Equ;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Validation;
|
||||
@@ -46,9 +47,9 @@ namespace NzbDrone.Core.Indexers
|
||||
}
|
||||
}
|
||||
|
||||
public class SeedCriteriaSettings
|
||||
public class SeedCriteriaSettings : PropertywiseEquatable<SeedCriteriaSettings>
|
||||
{
|
||||
[FieldDefinition(0, Type = FieldType.Textbox, Label = "IndexerSettingsSeedRatio", HelpText = "IndexerSettingsSeedRatioHelpText")]
|
||||
[FieldDefinition(0, Type = FieldType.Number, Label = "IndexerSettingsSeedRatio", HelpText = "IndexerSettingsSeedRatioHelpText")]
|
||||
public double? SeedRatio { get; set; }
|
||||
|
||||
[FieldDefinition(1, Type = FieldType.Number, Label = "IndexerSettingsSeedTime", Unit = "minutes", HelpText = "IndexerSettingsSeedTimeHelpText", Advanced = true)]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Equ;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Languages;
|
||||
@@ -17,9 +18,9 @@ namespace NzbDrone.Core.Indexers.TorrentRss
|
||||
}
|
||||
}
|
||||
|
||||
public class TorrentRssIndexerSettings : ITorrentIndexerSettings
|
||||
public class TorrentRssIndexerSettings : PropertywiseEquatable<TorrentRssIndexerSettings>, ITorrentIndexerSettings
|
||||
{
|
||||
private static readonly TorrentRssIndexerSettingsValidator Validator = new TorrentRssIndexerSettingsValidator();
|
||||
private static readonly TorrentRssIndexerSettingsValidator Validator = new ();
|
||||
|
||||
public TorrentRssIndexerSettings()
|
||||
{
|
||||
@@ -42,7 +43,7 @@ namespace NzbDrone.Core.Indexers.TorrentRss
|
||||
public int MinimumSeeders { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
|
||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new ();
|
||||
|
||||
[FieldDefinition(5, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
|
||||
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Equ;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Languages;
|
||||
@@ -18,9 +19,9 @@ namespace NzbDrone.Core.Indexers.Torrentleech
|
||||
}
|
||||
}
|
||||
|
||||
public class TorrentleechSettings : ITorrentIndexerSettings
|
||||
public class TorrentleechSettings : PropertywiseEquatable<TorrentleechSettings>, ITorrentIndexerSettings
|
||||
{
|
||||
private static readonly TorrentleechSettingsValidator Validator = new TorrentleechSettingsValidator();
|
||||
private static readonly TorrentleechSettingsValidator Validator = new ();
|
||||
|
||||
public TorrentleechSettings()
|
||||
{
|
||||
@@ -39,7 +40,7 @@ namespace NzbDrone.Core.Indexers.Torrentleech
|
||||
public int MinimumSeeders { get; set; }
|
||||
|
||||
[FieldDefinition(3)]
|
||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
|
||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new ();
|
||||
|
||||
[FieldDefinition(4, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
|
||||
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Equ;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
@@ -18,7 +19,7 @@ namespace NzbDrone.Core.Indexers.Torznab
|
||||
return settings.BaseUrl != null && ApiKeyWhiteList.Any(c => settings.BaseUrl.ToLowerInvariant().Contains(c));
|
||||
}
|
||||
|
||||
private static readonly Regex AdditionalParametersRegex = new Regex(@"(&.+?\=.+?)+", RegexOptions.Compiled);
|
||||
private static readonly Regex AdditionalParametersRegex = new (@"(&.+?\=.+?)+", RegexOptions.Compiled);
|
||||
|
||||
public TorznabSettingsValidator()
|
||||
{
|
||||
@@ -40,9 +41,11 @@ namespace NzbDrone.Core.Indexers.Torznab
|
||||
}
|
||||
}
|
||||
|
||||
public class TorznabSettings : NewznabSettings, ITorrentIndexerSettings
|
||||
public class TorznabSettings : NewznabSettings, ITorrentIndexerSettings, IEquatable<TorznabSettings>
|
||||
{
|
||||
private static readonly TorznabSettingsValidator Validator = new TorznabSettingsValidator();
|
||||
private static readonly TorznabSettingsValidator Validator = new ();
|
||||
|
||||
private static readonly MemberwiseEqualityComparer<TorznabSettings> Comparer = MemberwiseEqualityComparer<TorznabSettings>.ByProperties;
|
||||
|
||||
public TorznabSettings()
|
||||
{
|
||||
@@ -53,7 +56,7 @@ namespace NzbDrone.Core.Indexers.Torznab
|
||||
public int MinimumSeeders { get; set; }
|
||||
|
||||
[FieldDefinition(9)]
|
||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
|
||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new ();
|
||||
|
||||
[FieldDefinition(10, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
|
||||
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
|
||||
@@ -62,5 +65,20 @@ namespace NzbDrone.Core.Indexers.Torznab
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
|
||||
public bool Equals(TorznabSettings other)
|
||||
{
|
||||
return Comparer.Equals(this, other);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as TorznabSettings);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Comparer.GetHashCode(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"Added": "Afegit",
|
||||
"ApiKeyValidationHealthCheckMessage": "Actualitzeu la vostra clau de l'API perquè tingui almenys {length} caràcters. Podeu fer-ho mitjançant la configuració o el fitxer de configuració",
|
||||
"ApiKeyValidationHealthCheckMessage": "Actualitzeu la vostra clau API perquè tingui almenys {length} caràcters. Podeu fer-ho mitjançant la configuració o el fitxer de configuració",
|
||||
"AgeWhenGrabbed": "Edat (quan es captura)",
|
||||
"AuthenticationRequired": "Autenticació necessària",
|
||||
"AutomaticAdd": "Afegeix automàticament",
|
||||
@@ -49,7 +49,7 @@
|
||||
"Default": "Per defecte",
|
||||
"DeleteImportList": "Suprimeix la llista d'importació",
|
||||
"DeleteSelectedImportLists": "Suprimeix la(es) llista(es) d'importació",
|
||||
"DeletedReasonManual": "El fitxer va ser suprimit mitjançant la interfície d'usuari",
|
||||
"DeletedReasonManual": "El fitxer s'ha suprimit mitjançant {appName}, ja sigui manualment o amb una altra eina mitjançant l'API",
|
||||
"AbsoluteEpisodeNumbers": "Números d'episodis absoluts",
|
||||
"AirDate": "Data d'emissió",
|
||||
"DefaultNameCopiedProfile": "{name} - Còpia",
|
||||
@@ -225,8 +225,8 @@
|
||||
"BlocklistReleases": "Llista de llançaments bloquejats",
|
||||
"BuiltIn": "Integrat",
|
||||
"BrowserReloadRequired": "Es requereix una recàrrega del navegador",
|
||||
"ApplicationUrlHelpText": "URL extern d'aquesta aplicació, inclòs http(s)://, port i URL base",
|
||||
"AuthBasic": "Basic (finestra emergent del navegador)",
|
||||
"ApplicationUrlHelpText": "URL extern de l'aplicació, inclòs http(s)://, port i URL base",
|
||||
"AuthBasic": "Bàsic (finestra emergent del navegador)",
|
||||
"AutomaticSearch": "Cerca automàtica",
|
||||
"Automatic": "Automàtic",
|
||||
"BindAddress": "Adreça d'enllaç",
|
||||
@@ -239,13 +239,13 @@
|
||||
"BranchUpdate": "Branca que s'utilitza per a actualitzar {appName}",
|
||||
"CalendarLoadError": "No es pot carregar el calendari",
|
||||
"AudioInfo": "Informació d'àudio",
|
||||
"ApplyTagsHelpTextRemove": "Eliminar: elimina les etiquetes introduïdes",
|
||||
"ApplyTagsHelpTextRemove": "Eliminació: elimina les etiquetes introduïdes",
|
||||
"AutoAdd": "Afegeix automàticament",
|
||||
"Apply": "Aplica",
|
||||
"Backup": "Còpia de seguretat",
|
||||
"Blocklist": "Llista de bloquejats",
|
||||
"Calendar": "Calendari",
|
||||
"ApplyTagsHelpTextAdd": "Afegeix: afegeix les etiquetes a la llista d'etiquetes existent",
|
||||
"ApplyTagsHelpTextAdd": "Afegiment: afegeix les etiquetes a la llista d'etiquetes existent",
|
||||
"AptUpdater": "Utilitzeu apt per a instal·lar l'actualització",
|
||||
"BackupNow": "Fes ara la còpia de seguretat",
|
||||
"AppDataDirectory": "Directori AppData",
|
||||
@@ -411,7 +411,7 @@
|
||||
"PrioritySettings": "Prioritat: {priority}",
|
||||
"QualitiesLoadError": "No es poden carregar perfils de qualitat",
|
||||
"RemoveSelectedItemQueueMessageText": "Esteu segur que voleu eliminar 1 element de la cua?",
|
||||
"ResetAPIKeyMessageText": "Esteu segur que voleu restablir la clau de l'API?",
|
||||
"ResetAPIKeyMessageText": "Esteu segur que voleu restablir la clau API?",
|
||||
"TheLogLevelDefault": "El nivell de registre per defecte és \"Info\" i es pot canviar a [Configuració general](/configuració/general)",
|
||||
"ClickToChangeLanguage": "Feu clic per a canviar l'idioma",
|
||||
"ClickToChangeEpisode": "Feu clic per a canviar l'episodi",
|
||||
@@ -669,7 +669,7 @@
|
||||
"AutoTaggingSpecificationOriginalLanguage": "Llenguatge",
|
||||
"AutoTaggingSpecificationQualityProfile": "Perfil de Qualitat",
|
||||
"AutoTaggingSpecificationRootFolder": "Carpeta arrel",
|
||||
"AddDelayProfileError": "No s'ha pogut afegir un perfil realentit, torna-ho a probar",
|
||||
"AddDelayProfileError": "No s'ha pogut afegir un perfil de retard, torna-ho a provar.",
|
||||
"AutoTaggingSpecificationSeriesType": "Tipus de Sèries",
|
||||
"AutoTaggingSpecificationStatus": "Estat",
|
||||
"BlocklistAndSearch": "Llista de bloqueig i cerca",
|
||||
@@ -711,5 +711,46 @@
|
||||
"MinutesSixty": "60 minuts: {sixty}",
|
||||
"CustomFilter": "Filtres personalitzats",
|
||||
"CustomFormatsSpecificationRegularExpressionHelpText": "El format personalitzat RegEx no distingeix entre majúscules i minúscules",
|
||||
"CustomFormatsSpecificationFlag": "Bandera"
|
||||
"CustomFormatsSpecificationFlag": "Bandera",
|
||||
"DeleteSelectedSeries": "Suprimeix les sèries seleccionades",
|
||||
"DeleteSeriesFolder": "Suprimeix la carpeta de sèries",
|
||||
"DeleteSeriesFolderConfirmation": "La carpeta de sèries '{path}' i tot el seu contingut es suprimiran.",
|
||||
"File": "Fitxer",
|
||||
"FilterContains": "conté",
|
||||
"FilterIs": "és",
|
||||
"Ok": "D'acord",
|
||||
"Qualities": "Qualitats",
|
||||
"DeleteSeriesFolderCountConfirmation": "Esteu segur que voleu suprimir {count} sèries seleccionades?",
|
||||
"Forecast": "Previsió",
|
||||
"Paused": "En pausa",
|
||||
"Range": "Rang",
|
||||
"Today": "Avui",
|
||||
"Month": "Mes",
|
||||
"Reason": "Raó",
|
||||
"Pending": "Pendents",
|
||||
"FilterEqual": "igual",
|
||||
"Global": "Global",
|
||||
"Grab": "Captura",
|
||||
"Logout": "Tanca la sessió",
|
||||
"Filter": "Filtre",
|
||||
"Local": "Local",
|
||||
"Week": "Setmana",
|
||||
"AutoTaggingSpecificationTag": "Etiqueta",
|
||||
"WhatsNew": "Novetats",
|
||||
"DownloadClientSettingsUrlBaseHelpText": "Afegeix un prefix a l'URL {clientName}, com ara {url}",
|
||||
"IndexerHDBitsSettingsCodecs": "Còdecs",
|
||||
"DownloadClientRTorrentSettingsDirectoryHelpText": "Ubicació opcional de les baixades completades, deixeu-lo en blanc per utilitzar la ubicació predeterminada de rTorrent",
|
||||
"IndexerHDBitsSettingsMediums": "Mitjans",
|
||||
"UpdateStartupTranslocationHealthCheckMessage": "No es pot instal·lar l'actualització perquè la carpeta d'inici '{startupFolder}' es troba en una carpeta de translocació d'aplicacions.",
|
||||
"UpdateStartupNotWritableHealthCheckMessage": "No es pot instal·lar l'actualització perquè l'usuari '{userName}' no té permisos d'escriptura de la carpeta d'inici '{startupFolder}'.",
|
||||
"DownloadClientTransmissionSettingsDirectoryHelpText": "Ubicació opcional per a les baixades, deixeu-lo en blanc per utilitzar la ubicació predeterminada de Transmission",
|
||||
"Mixed": "Combinat",
|
||||
"Proxy": "Servidor intermediari",
|
||||
"Mapping": "Mapejat",
|
||||
"More": "Més",
|
||||
"MultiLanguages": "Multi-idioma",
|
||||
"Organize": "Organitza",
|
||||
"Search": "Cerca",
|
||||
"SelectDropdown": "Seleccioneu...",
|
||||
"Shutdown": "Apaga"
|
||||
}
|
||||
|
||||
@@ -158,6 +158,7 @@
|
||||
"BlocklistAndSearch": "Blocklist and Search",
|
||||
"BlocklistAndSearchHint": "Start a search for a replacement after blocklisting",
|
||||
"BlocklistAndSearchMultipleHint": "Start searches for replacements after blocklisting",
|
||||
"BlocklistFilterHasNoItems": "Selected blocklist filter contains no items",
|
||||
"BlocklistLoadError": "Unable to load blocklist",
|
||||
"BlocklistMultipleOnlyHint": "Blocklist without searching for replacements",
|
||||
"BlocklistOnly": "Blocklist Only",
|
||||
@@ -248,8 +249,8 @@
|
||||
"ConnectionLost": "Connection Lost",
|
||||
"ConnectionLostReconnect": "{appName} will try to connect automatically, or you can click reload below.",
|
||||
"ConnectionLostToBackend": "{appName} has lost its connection to the backend and will need to be reloaded to restore functionality.",
|
||||
"Connections": "Connections",
|
||||
"ConnectionSettingsUrlBaseHelpText": "Adds a prefix to the {connectionName} url, such as {url}",
|
||||
"Connections": "Connections",
|
||||
"Continuing": "Continuing",
|
||||
"ContinuingOnly": "Continuing Only",
|
||||
"ContinuingSeriesDescription": "More episodes/another season is expected",
|
||||
@@ -280,8 +281,8 @@
|
||||
"CustomFormats": "Custom Formats",
|
||||
"CustomFormatsLoadError": "Unable to load Custom Formats",
|
||||
"CustomFormatsSettings": "Custom Formats Settings",
|
||||
"CustomFormatsSettingsTriggerInfo": "A Custom Format will be applied to a release or file when it matches at least one of each of the different condition types chosen.",
|
||||
"CustomFormatsSettingsSummary": "Custom Formats and Settings",
|
||||
"CustomFormatsSettingsTriggerInfo": "A Custom Format will be applied to a release or file when it matches at least one of each of the different condition types chosen.",
|
||||
"CustomFormatsSpecificationFlag": "Flag",
|
||||
"CustomFormatsSpecificationLanguage": "Language",
|
||||
"CustomFormatsSpecificationMaximumSize": "Maximum Size",
|
||||
@@ -307,6 +308,7 @@
|
||||
"Date": "Date",
|
||||
"Dates": "Dates",
|
||||
"Day": "Day",
|
||||
"DayOfWeekAt": "{day} at {time}",
|
||||
"Debug": "Debug",
|
||||
"Default": "Default",
|
||||
"DefaultCase": "Default Case",
|
||||
@@ -410,16 +412,16 @@
|
||||
"DownloadClientAriaSettingsDirectoryHelpText": "Optional location to put downloads in, leave blank to use the default Aria2 location",
|
||||
"DownloadClientCheckNoneAvailableHealthCheckMessage": "No download client is available",
|
||||
"DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Unable to communicate with {downloadClientName}. {errorMessage}",
|
||||
"DownloadClientDelugeSettingsDirectory": "Download Directory",
|
||||
"DownloadClientDelugeSettingsDirectoryCompleted": "Move When Completed Directory",
|
||||
"DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Optional location to move completed downloads to, leave blank to use the default Deluge location",
|
||||
"DownloadClientDelugeSettingsDirectoryHelpText": "Optional location to put downloads in, leave blank to use the default Deluge location",
|
||||
"DownloadClientDelugeSettingsUrlBaseHelpText": "Adds a prefix to the deluge json url, see {url}",
|
||||
"DownloadClientDelugeTorrentStateError": "Deluge is reporting an error",
|
||||
"DownloadClientDelugeValidationLabelPluginFailure": "Configuration of label failed",
|
||||
"DownloadClientDelugeValidationLabelPluginFailureDetail": "{appName} was unable to add the label to {clientName}.",
|
||||
"DownloadClientDelugeValidationLabelPluginInactive": "Label plugin not activated",
|
||||
"DownloadClientDelugeValidationLabelPluginInactiveDetail": "You must have the Label plugin enabled in {clientName} to use categories.",
|
||||
"DownloadClientDelugeSettingsDirectory": "Download Directory",
|
||||
"DownloadClientDelugeSettingsDirectoryHelpText": "Optional location to put downloads in, leave blank to use the default Deluge location",
|
||||
"DownloadClientDelugeSettingsDirectoryCompleted": "Move When Completed Directory",
|
||||
"DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Optional location to move completed downloads to, leave blank to use the default Deluge location",
|
||||
"DownloadClientDownloadStationProviderMessage": "{appName} is unable to connect to Download Station if 2-Factor Authentication is enabled on your DSM account",
|
||||
"DownloadClientDownloadStationSettingsDirectoryHelpText": "Optional shared folder to put downloads into, leave blank to use the default Download Station location",
|
||||
"DownloadClientDownloadStationValidationApiVersion": "Download Station API version not supported, should be at least {requiredVersion}. It supports from {minVersion} to {maxVersion}",
|
||||
@@ -751,6 +753,7 @@
|
||||
"Group": "Group",
|
||||
"HardlinkCopyFiles": "Hardlink/Copy Files",
|
||||
"HasMissingSeason": "Has Missing Season",
|
||||
"HasUnmonitoredSeason": "Has Unmonitored Season",
|
||||
"Health": "Health",
|
||||
"HealthMessagesInfoBox": "You can find more information about the cause of these health check messages by clicking the wiki link (book icon) at the end of the row, or by checking your [logs]({link}). If you have difficulty interpreting these messages then you can reach out to our support, at the links below.",
|
||||
"Here": "here",
|
||||
@@ -867,12 +870,12 @@
|
||||
"ImportListsSimklSettingsUserListTypePlanToWatch": "Plan To Watch",
|
||||
"ImportListsSimklSettingsUserListTypeWatching": "Watching",
|
||||
"ImportListsSonarrSettingsApiKeyHelpText": "API Key of the {appName} instance to import from",
|
||||
"ImportListsSonarrSettingsSyncSeasonMonitoring": "Sync Season Monitoring",
|
||||
"ImportListsSonarrSettingsSyncSeasonMonitoringHelpText": "Sync season monitoring from {appName} instance, if enabled 'Monitor' will be ignored",
|
||||
"ImportListsSonarrSettingsFullUrl": "Full URL",
|
||||
"ImportListsSonarrSettingsFullUrlHelpText": "URL, including port, of the {appName} instance to import from",
|
||||
"ImportListsSonarrSettingsQualityProfilesHelpText": "Quality Profiles from the source instance to import from",
|
||||
"ImportListsSonarrSettingsRootFoldersHelpText": "Root Folders from the source instance to import from",
|
||||
"ImportListsSonarrSettingsSyncSeasonMonitoring": "Sync Season Monitoring",
|
||||
"ImportListsSonarrSettingsSyncSeasonMonitoringHelpText": "Sync season monitoring from {appName} instance, if enabled 'Monitor' will be ignored",
|
||||
"ImportListsSonarrSettingsTagsHelpText": "Tags from the source instance to import from",
|
||||
"ImportListsSonarrValidationInvalidUrl": "{appName} URL is invalid, are you missing a URL base?",
|
||||
"ImportListsTraktSettingsAdditionalParameters": "Additional Parameters",
|
||||
@@ -976,11 +979,11 @@
|
||||
"IndexerSettingsCookieHelpText": "If your site requires a login cookie to access the rss, you'll have to retrieve it via a browser.",
|
||||
"IndexerSettingsMinimumSeeders": "Minimum Seeders",
|
||||
"IndexerSettingsMinimumSeedersHelpText": "Minimum number of seeders required.",
|
||||
"IndexerSettingsMultiLanguageRelease": "Multi Languages",
|
||||
"IndexerSettingsMultiLanguageReleaseHelpText": "What languages are normally in a multi release on this indexer?",
|
||||
"IndexerSettingsPasskey": "Passkey",
|
||||
"IndexerSettingsRejectBlocklistedTorrentHashes": "Reject Blocklisted Torrent Hashes While Grabbing",
|
||||
"IndexerSettingsRejectBlocklistedTorrentHashesHelpText": "If a torrent is blocked by hash it may not properly be rejected during RSS/Search for some indexers, enabling this will allow it to be rejected after the torrent is grabbed, but before it is sent to the client.",
|
||||
"IndexerSettingsMultiLanguageRelease": "Multi Languages",
|
||||
"IndexerSettingsMultiLanguageReleaseHelpText": "What languages are normally in a multi release on this indexer?",
|
||||
"IndexerSettingsRssUrl": "RSS URL",
|
||||
"IndexerSettingsRssUrlHelpText": "Enter to URL to an {indexer} compatible RSS feed",
|
||||
"IndexerSettingsSeasonPackSeedTime": "Season-Pack Seed Time",
|
||||
@@ -1790,7 +1793,6 @@
|
||||
"SelectSeries": "Select Series",
|
||||
"SendAnonymousUsageData": "Send Anonymous Usage Data",
|
||||
"Series": "Series",
|
||||
"SeriesFootNote": "Optionally control truncation to a maximum number of bytes including ellipsis (`...`). Truncating from the end (e.g. `{Series Title:30}`) or the beginning (e.g. `{Series Title:-30}`) are both supported.",
|
||||
"SeriesAndEpisodeInformationIsProvidedByTheTVDB": "Series and episode information is provided by TheTVDB.com. [Please consider supporting them]({url}) .",
|
||||
"SeriesCannotBeFound": "Sorry, that series cannot be found.",
|
||||
"SeriesDetailsCountEpisodeFiles": "{episodeFileCount} episode files",
|
||||
@@ -1804,6 +1806,7 @@
|
||||
"SeriesFolderFormat": "Series Folder Format",
|
||||
"SeriesFolderFormatHelpText": "Used when adding a new series or moving series via the series editor",
|
||||
"SeriesFolderImportedTooltip": "Episode imported from series folder",
|
||||
"SeriesFootNote": "Optionally control truncation to a maximum number of bytes including ellipsis (`...`). Truncating from the end (e.g. `{Series Title:30}`) or the beginning (e.g. `{Series Title:-30}`) are both supported.",
|
||||
"SeriesID": "Series ID",
|
||||
"SeriesIndexFooterContinuing": "Continuing (All episodes downloaded)",
|
||||
"SeriesIndexFooterDownloading": "Downloading (One or more episodes)",
|
||||
@@ -1947,10 +1950,12 @@
|
||||
"Title": "Title",
|
||||
"Titles": "Titles",
|
||||
"Today": "Today",
|
||||
"TodayAt": "Today at {time}",
|
||||
"ToggleMonitoredSeriesUnmonitored ": "Cannot toggle monitored state when series is unmonitored",
|
||||
"ToggleMonitoredToUnmonitored": "Monitored, click to unmonitor",
|
||||
"ToggleUnmonitoredToMonitored": "Unmonitored, click to monitor",
|
||||
"Tomorrow": "Tomorrow",
|
||||
"TomorrowAt": "Tomorrow at {time}",
|
||||
"TorrentBlackhole": "Torrent Blackhole",
|
||||
"TorrentBlackholeSaveMagnetFiles": "Save Magnet Files",
|
||||
"TorrentBlackholeSaveMagnetFilesExtension": "Save Magnet Files Extension",
|
||||
@@ -2072,5 +2077,6 @@
|
||||
"Year": "Year",
|
||||
"Yes": "Yes",
|
||||
"YesCancel": "Yes, Cancel",
|
||||
"Yesterday": "Yesterday"
|
||||
"Yesterday": "Yesterday",
|
||||
"YesterdayAt": "Yesterday at {time}"
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
"Condition": "Condición",
|
||||
"Component": "Componente",
|
||||
"Custom": "Personalizado",
|
||||
"Cutoff": "Umbral",
|
||||
"Cutoff": "Límite",
|
||||
"Dates": "Fechas",
|
||||
"Debug": "Debug",
|
||||
"Date": "Fecha",
|
||||
@@ -181,7 +181,7 @@
|
||||
"AddNewSeriesError": "Falló al cargar los resultados de la búsqueda, inténtelo de nuevo.",
|
||||
"AddNewSeriesHelpText": "Es fácil añadir una nueva serie, empiece escribiendo el nombre de la serie que desea añadir.",
|
||||
"AddNewSeriesRootFolderHelpText": "La subcarpeta '{folder}' será creada automáticamente",
|
||||
"AddNewSeriesSearchForCutoffUnmetEpisodes": "Empezar la búsqueda de episodios con umbrales no alcanzados",
|
||||
"AddNewSeriesSearchForCutoffUnmetEpisodes": "Empezar la búsqueda de episodios con límites no alcanzados",
|
||||
"AddNewSeriesSearchForMissingEpisodes": "Empezar búsqueda de episodios faltantes",
|
||||
"AddQualityProfile": "Añadir Perfil de Calidad",
|
||||
"AddQualityProfileError": "No se pudo añadir un nuevo perfil de calidad, inténtelo de nuevo.",
|
||||
@@ -249,7 +249,7 @@
|
||||
"ConnectionLostToBackend": "{appName} ha perdido su conexión con el backend y tendrá que ser recargado para restaurar su funcionalidad.",
|
||||
"CalendarOptions": "Opciones de Calendario",
|
||||
"BypassDelayIfAboveCustomFormatScoreHelpText": "Habilitar ignorar cuando la versión tenga una puntuación superior a la puntuación mínima configurada para el formato personalizado",
|
||||
"Default": "Por defecto",
|
||||
"Default": "Predeterminado",
|
||||
"DeleteBackupMessageText": "Seguro que quieres eliminar la copia de seguridad '{name}'?",
|
||||
"DeleteAutoTagHelpText": "¿Está seguro de querer eliminar el etiquetado automático '{name}'?",
|
||||
"AddImportListImplementation": "Añadir lista de importación - {implementationName}",
|
||||
@@ -272,7 +272,7 @@
|
||||
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Confirma la nueva contraseña",
|
||||
"MonitorPilotEpisode": "Episodio Piloto",
|
||||
"MonitorRecentEpisodesDescription": "Monitorizar episodios emitidos en los últimos 90 días y los episodios futuros",
|
||||
"MonitorSelected": "Monitorizar Seleccionados",
|
||||
"MonitorSelected": "Monitorizar seleccionados",
|
||||
"MonitorSeries": "Monitorizar Series",
|
||||
"NoHistory": "Sin historial",
|
||||
"NoHistoryFound": "No se encontró historial",
|
||||
@@ -436,7 +436,7 @@
|
||||
"CustomFormatsLoadError": "No se pudo cargar Formatos Personalizados",
|
||||
"CustomFormatsSettings": "Configuración de formatos personalizados",
|
||||
"CustomFormatsSettingsSummary": "Formatos y configuraciones personalizados",
|
||||
"CutoffUnmet": "Umbrales no alcanzados",
|
||||
"CutoffUnmet": "Límite no alcanzado",
|
||||
"DailyEpisodeFormat": "Formato de episodio diario",
|
||||
"Database": "Base de datos",
|
||||
"DelayMinutes": "{delay} minutos",
|
||||
@@ -463,14 +463,14 @@
|
||||
"InteractiveImportLoadError": "No se pueden cargar elementos de la importación manual",
|
||||
"InteractiveImportNoFilesFound": "No se han encontrado archivos de vídeo en la carpeta seleccionada",
|
||||
"InteractiveImportNoQuality": "La calidad debe elegirse para cada archivo seleccionado",
|
||||
"InteractiveSearchModalHeader": "Búsqueda Interactiva",
|
||||
"InteractiveSearchModalHeader": "Búsqueda interactiva",
|
||||
"InvalidUILanguage": "Su interfaz de usuario está configurada en un idioma no válido, corríjalo y guarde la configuración",
|
||||
"ChownGroup": "chown grupo",
|
||||
"DelayProfileProtocol": "Protocolo: {preferredProtocol}",
|
||||
"DelayProfilesLoadError": "Incapaz de cargar Perfiles de Retardo",
|
||||
"ContinuingSeriesDescription": "Se esperan más episodios u otra temporada",
|
||||
"CutoffUnmetLoadError": "Error cargando elementos con umbrales no alcanzados",
|
||||
"CutoffUnmetNoItems": "No hay elementos con umbrales no alcanzados",
|
||||
"CutoffUnmetLoadError": "Error cargando elementos con límites no alcanzados",
|
||||
"CutoffUnmetNoItems": "Ningún elemento con límites no alcanzados",
|
||||
"DelayProfile": "Perfil de retardo",
|
||||
"Delete": "Eliminar",
|
||||
"DeleteDelayProfile": "Eliminar Perfil de Retardo",
|
||||
@@ -530,7 +530,7 @@
|
||||
"DeleteEpisodesFilesHelpText": "Eliminar archivos de episodios y directorio de series",
|
||||
"DoNotPrefer": "No preferir",
|
||||
"DoNotUpgradeAutomatically": "No actualizar automáticamente",
|
||||
"IndexerSettingsSeedRatioHelpText": "El ratio que un torrent debería alcanzar antes de detenerse, vacío usa el predeterminado del cliente de descarga. El ratio debería ser al menos 1.0 y seguir las reglas de los indexadores",
|
||||
"IndexerSettingsSeedRatioHelpText": "El ratio que un torrent debería alcanzar antes de parar, vacío usa el predeterminado del cliente de descarga. El ratio debería ser al menos 1.0 y seguir las reglas de los indexadores",
|
||||
"Download": "Descargar",
|
||||
"Donate": "Donar",
|
||||
"DownloadClientDelugeValidationLabelPluginFailure": "La configuración de etiqueta falló",
|
||||
@@ -816,7 +816,7 @@
|
||||
"FirstDayOfWeek": "Primer día de la semana",
|
||||
"Forums": "Foros",
|
||||
"FreeSpace": "Espacio libre",
|
||||
"MissingNoItems": "No hay elementos faltantes",
|
||||
"MissingNoItems": "Ningún elemento faltante",
|
||||
"ImportListRootFolderMissingRootHealthCheckMessage": "Carpeta raíz faltante para importar lista(s): {rootFolderInfo}",
|
||||
"MissingLoadError": "Error cargando elementos faltantes",
|
||||
"RemoveSelectedBlocklistMessageText": "¿Estás seguro que quieres eliminar los elementos seleccionados de la lista de bloqueos?",
|
||||
@@ -890,8 +890,8 @@
|
||||
"ImportListSettings": "Importar ajustes de lista",
|
||||
"ImportListsSettingsSummary": "Importar desde otra instancia de {appName} o listas de Trakt y gestionar exclusiones de lista",
|
||||
"IncludeCustomFormatWhenRenamingHelpText": "Incluir en formato de renombrado {Custom Formats}",
|
||||
"QualityCutoffNotMet": "Calidad del umbral que no ha sido alcanzado",
|
||||
"SearchForCutoffUnmetEpisodesConfirmationCount": "¿Estás seguro que quieres buscar los {totalRecords} episodios en Umbrales no alcanzados?",
|
||||
"QualityCutoffNotMet": "No se ha alcanzado el límite de calidad",
|
||||
"SearchForCutoffUnmetEpisodesConfirmationCount": "¿Estás seguro que quieres buscar los {totalRecords} episodios con límites no alcanzados?",
|
||||
"IndexerOptionsLoadError": "No se pudieron cargar las opciones del indexador",
|
||||
"IndexerIPTorrentsSettingsFeedUrl": "URL del canal",
|
||||
"ICalFeed": "Canal de iCal",
|
||||
@@ -991,7 +991,7 @@
|
||||
"ImportScriptPathHelpText": "La ruta al script a usar para importar",
|
||||
"ImportUsingScriptHelpText": "Copiar archivos para importar usando un script (p. ej. para transcodificación)",
|
||||
"Importing": "Importando",
|
||||
"IncludeUnmonitored": "Incluir sin monitorizar",
|
||||
"IncludeUnmonitored": "Incluir no monitorizadas",
|
||||
"IndexerLongTermStatusAllUnavailableHealthCheckMessage": "Ningún indexador está disponible debido a errores durante más de 6 horas",
|
||||
"IRC": "IRC",
|
||||
"ICalShowAsAllDayEvents": "Mostrar como eventos para todo el día",
|
||||
@@ -1041,7 +1041,7 @@
|
||||
"ImportedTo": "Importar a",
|
||||
"IncludeCustomFormatWhenRenaming": "Incluir formato personalizado cuando se renombra",
|
||||
"CleanLibraryLevel": "Limpiar el nivel de la librería",
|
||||
"SearchForCutoffUnmetEpisodes": "Buscar todos los episodios en Umbrales no alcanzados",
|
||||
"SearchForCutoffUnmetEpisodes": "Buscar todos los episodios con límites no alcanzados",
|
||||
"IconForSpecials": "Icono para Especiales",
|
||||
"ImportListExclusions": "Importar lista de exclusiones",
|
||||
"ImportListStatusAllPossiblePartialFetchHealthCheckMessage": "Todas las listas requieren interacción manual debido a posibles búsquedas parciales",
|
||||
@@ -1057,8 +1057,8 @@
|
||||
"HourShorthand": "h",
|
||||
"ICalFeedHelpText": "Copia esta URL a tu(s) cliente(s) o pulsa para suscribirse si tu navegador soporta Webcal",
|
||||
"ICalTagsSeriesHelpText": "El canal solo contendrá series con al menos una etiqueta coincidente",
|
||||
"IconForCutoffUnmet": "Icono para Umbrales no alcanzados",
|
||||
"IconForCutoffUnmetHelpText": "Mostrar icono para archivos cuando el umbral no haya sido alcanzado",
|
||||
"IconForCutoffUnmet": "Icono de límite no alcanzado",
|
||||
"IconForCutoffUnmetHelpText": "Muestra un icono para archivos cuando el límite no haya sido alcanzado",
|
||||
"EpisodeCount": "Recuento de episodios",
|
||||
"IndexerSettings": "Opciones del indexador",
|
||||
"AddDelayProfileError": "No se pudo añadir un nuevo perfil de retraso, por favor inténtalo de nuevo.",
|
||||
@@ -1102,7 +1102,7 @@
|
||||
"IndexerSettingsRssUrlHelpText": "Introduzca la URL de un canal RSS compatible con {indexer}",
|
||||
"IndexerStatusUnavailableHealthCheckMessage": "Indexadores no disponibles debido a errores: {indexerNames}",
|
||||
"IndexerHDBitsSettingsMediums": "Medios",
|
||||
"IndexerSettingsSeedTimeHelpText": "El tiempo que un torrent debería ser compartido antes de detenerse, vació usa el predeterminado del cliente de descarga",
|
||||
"IndexerSettingsSeedTimeHelpText": "El tiempo que un torrent debería ser sembrado antes de parar, vacío usa el predeterminado del cliente de descarga",
|
||||
"IndexerValidationCloudFlareCaptchaRequired": "Sitio protegido por CloudFlare CAPTCHA. Se requiere un token CAPTCHA válido.",
|
||||
"NotificationsEmailSettingsUseEncryption": "Usar Cifrado",
|
||||
"LastDuration": "Última Duración",
|
||||
@@ -1226,7 +1226,7 @@
|
||||
"MetadataSourceSettings": "Opciones de fuente de metadatos",
|
||||
"MetadataSettingsSeriesSummary": "Crea archivos de metadatos cuando los episodios son importados o las series son refrescadas",
|
||||
"Metadata": "Metadatos",
|
||||
"MassSearchCancelWarning": "Esto no puede ser cancelado una vez empiece sin reiniciar {appName} o deshabilitar todos tus indexadores.",
|
||||
"MassSearchCancelWarning": "No puede ser cancelado una vez comience sin reiniciar {appName} o deshabilitar todos tus indexadores.",
|
||||
"MaximumSingleEpisodeAgeHelpText": "Durante una búsqueda completa de temporada, solo serán permitidos los paquetes de temporada cuando el último episodio de temporada sea más antiguo que esta configuración. Solo series estándar. Usa 0 para deshabilitar.",
|
||||
"MaximumSize": "Tamaño máximo",
|
||||
"IndexerValidationNoResultsInConfiguredCategories": "Petición con éxito, pero no se devolvió ningún resultado en las categorías configuradas de tu indexador. Esto puede ser un problema con el indexador o tus ajustes de categoría de tu indexador.",
|
||||
@@ -1710,7 +1710,7 @@
|
||||
"SeriesIndexFooterEnded": "FInalizado (Todos los episodios descargados)",
|
||||
"ShowDateAdded": "Mostrar fecha de adición",
|
||||
"UnmonitorDeletedEpisodesHelpText": "Los episodios borrados del disco son dejados de monitorizar automáticamente en {appName}",
|
||||
"UnmonitorSelected": "Dejar de monitorizar seleccionados",
|
||||
"UnmonitorSelected": "No monitorizar seleccionados",
|
||||
"UpdateSelected": "Actualizar seleccionados",
|
||||
"UpdateUiNotWritableHealthCheckMessage": "No se puede instalar la actualización porque la carpeta de interfaz '{uiFolder}' no es modificable por el usuario '{userName}'.",
|
||||
"UpgradeUntil": "Actualizar hasta",
|
||||
@@ -1964,7 +1964,7 @@
|
||||
"NotificationsValidationInvalidAuthenticationToken": "Token de autenticación inválido",
|
||||
"NotificationsValidationUnableToConnect": "No se pudo conectar: {exceptionMessage}",
|
||||
"NotificationsValidationUnableToSendTestMessageApiResponse": "No se pudo enviar un mensaje de prueba. Respuesta de la API: {error}",
|
||||
"OverrideGrabModalTitle": "Sobrescribe y captura - {title}",
|
||||
"OverrideGrabModalTitle": "Sobrescribir y capturar - {title}",
|
||||
"ReleaseProfileTagSeriesHelpText": "Los perfiles de lanzamientos se aplicarán a series con al menos una etiqueta coincidente. Deja en blanco para aplicar a todas las series",
|
||||
"ReleaseSceneIndicatorMappedNotRequested": "El episodio mapeado no fue solicitado en esta búsqueda.",
|
||||
"RemotePathMappingBadDockerPathHealthCheckMessage": "Estás usando docker; el cliente de descarga {downloadClientName} ubica las descargas en {path} pero esta no es una ruta {osName} válida. Revisa tus mapeos de ruta remotos y opciones del cliente de descarga.",
|
||||
@@ -2071,5 +2071,8 @@
|
||||
"NotificationsTelegramSettingsIncludeAppName": "Incluir {appName} en el título",
|
||||
"NotificationsTelegramSettingsIncludeAppNameHelpText": "Opcionalmente prefija el título del mensaje con {appName} para diferenciar las notificaciones de las diferentes aplicaciones",
|
||||
"IndexerSettingsMultiLanguageRelease": "Múltiples idiomas",
|
||||
"IndexerSettingsMultiLanguageReleaseHelpText": "¿Qué idiomas están normalmente en un lanzamiento múltiple en este indexador?"
|
||||
"IndexerSettingsMultiLanguageReleaseHelpText": "¿Qué idiomas están normalmente en un lanzamiento múltiple en este indexador?",
|
||||
"DownloadClientQbittorrentTorrentStateMissingFiles": "qBittorrent está reportando archivos faltantes",
|
||||
"BlocklistFilterHasNoItems": "El filtro de lista de bloqueo seleccionado no contiene elementos",
|
||||
"HasUnmonitoredSeason": "Tiene temporada sin monitorizar"
|
||||
}
|
||||
|
||||
@@ -1884,7 +1884,7 @@
|
||||
"CustomFormatsSpecificationSource": "Source",
|
||||
"ImportListsAniListSettingsAuthenticateWithAniList": "S'authentifier avec AniList",
|
||||
"ImportListsAniListSettingsImportCancelled": "Importation annulée",
|
||||
"ImportListsAniListSettingsImportCancelledHelpText": "Médias : La série est annulée",
|
||||
"ImportListsAniListSettingsImportCancelledHelpText": "Médias : Série annulée",
|
||||
"ImportListsAniListSettingsImportCompleted": "Importation terminée",
|
||||
"ImportListsAniListSettingsImportFinished": "Importation terminée",
|
||||
"ImportListsAniListSettingsUsernameHelpText": "Nom d'utilisateur pour la liste à importer",
|
||||
|
||||
@@ -186,5 +186,28 @@
|
||||
"AutomaticSearch": "Pesquisa automática",
|
||||
"AutoTaggingRequiredHelpText": "Esta condição de {implementationName} deve corresponder para que a regra de marcação automática seja aplicada. Caso contrário, uma única correspondência de {implementationName} é suficiente.",
|
||||
"Backup": "Backup",
|
||||
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Confirmar nova senha"
|
||||
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Confirmar nova senha",
|
||||
"Conditions": "Condições",
|
||||
"ClickToChangeIndexerFlags": "Clique para alterar os sinalizadores do indexador",
|
||||
"CustomFormatsSpecificationRegularExpression": "Expressão regular (regex)",
|
||||
"Dash": "Traço",
|
||||
"ConnectionSettingsUrlBaseHelpText": "Adiciona um prefixo ao URL {connectionName}, como {url}",
|
||||
"CustomFormatsSpecificationFlag": "Sinalizar",
|
||||
"CustomFormatsSpecificationRegularExpressionHelpText": "O regex do formato personalizado não diferencia maiúsculas e minúsculas",
|
||||
"AutoTaggingSpecificationTag": "Etiqueta",
|
||||
"ClearBlocklist": "Limpar lista de bloqueio",
|
||||
"ClearBlocklistMessageText": "Tem a certeza de que deseja limpar todos os itens da lista de bloqueio?",
|
||||
"CustomFormatsSettingsTriggerInfo": "Um formato personalizado será aplicado a uma versão ou ficheiro quando corresponder a pelo menos um dos diferentes tipos de condição escolhidos.",
|
||||
"BlocklistOnly": "Apenas adicionar à lista de bloqueio",
|
||||
"BlocklistAndSearch": "Adicionar à lista de bloqueio e pesquisar",
|
||||
"BlocklistAndSearchHint": "Iniciar uma pesquisa por um substituto após adicionar à lista de bloqueio",
|
||||
"BlocklistAndSearchMultipleHint": "Iniciar pesquisas por substitutos após adicionar à lista de bloqueio",
|
||||
"BlocklistMultipleOnlyHint": "Adicionar à lista de bloqueio sem procurar por substitutos",
|
||||
"BlocklistOnlyHint": "Adicionar à lista de bloqueio sem procurar por um substituto",
|
||||
"ChangeCategory": "Alterar categoria",
|
||||
"ChangeCategoryHint": "Altera o download para a \"Categoria pós-importação\" do cliente de download",
|
||||
"ChangeCategoryMultipleHint": "Altera os downloads para a \"Categoria pós-importação' do cliente de download",
|
||||
"ChownGroup": "Fazer chown em grupo",
|
||||
"Clone": "Clonar",
|
||||
"ContinuingOnly": "Continuando apenas"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user