Compare commits

...

35 Commits

Author SHA1 Message Date
Bogdan
0660b697ed New: Add Monitored specification to Auto Tagging
(cherry picked from commit 8bd91bd86b5955da5d19c9f866e7de701b843e7b)
2023-08-01 23:59:43 +00:00
Mark McDowall
704635f758 New: Book interactive search modal size
(cherry picked from commit 1f619e27f1e8905bc96ce54c483171469d204650)

Closes #2243
2023-08-01 18:32:14 +03:00
Bogdan
263e807de2 Ensure yarn packages are installed when running only LintUI 2023-07-31 08:33:52 +03:00
Mark McDowall
9ac9bd25c1 Re-order frontend build steps
(cherry picked from commit 97ad6682f7d54af8886144bc5a179fa7242f1f1f)
2023-07-31 08:01:50 +03:00
Bogdan
4ead5186ae Bump version to 0.3.1 2023-07-30 09:12:56 +03:00
Bogdan
dea797c375 Fixed: (UI) Ensure root folders are populated in Author Editor 2023-07-30 05:10:21 +03:00
PearsonFlyer
58ba24762b Fixed: Correctly calculate books count on Author page
Closes #1931
2023-07-30 05:10:07 +03:00
Servarr
fbd7b4fe33 Automated API Docs update [skip ci] 2023-07-30 05:09:25 +03:00
Mark McDowall
fee7fbbff6 New: Add result to commands to report commands that did not complete successfully
(cherry picked from commit 103ce3def4636ef891e72bd687ef8f46b5125233)
2023-07-30 03:44:14 +03:00
Taloth Saldono
18253a298e Log Goodreads connection failures with more info.
(cherry picked from commit 6672650b6b5e152e82fb3ad38a0a158d66c0b83d)
2023-07-30 03:44:14 +03:00
Bogdan
22f92150c3 Ensure original data is shown when no matches are made 2023-07-29 18:32:54 +03:00
Bogdan
4d7a762ee8 Fix book tests 2023-07-29 14:59:47 +03:00
Stevie Robinson
b11517e2ac Extend InlineMarkdown to handle code blocks in backticks
(cherry picked from commit e1c5533efa397632becc606c17232f97055e371b)
2023-07-29 09:59:50 +03:00
Bogdan
d5af254f47 Fix AuthorLookupFixture 2023-07-29 09:04:22 +03:00
Bogdan
f09da06f80 More test fixes 2023-07-29 07:48:47 +03:00
Bogdan
d3443510b4 Rename formatPreferredWordScore to formatCustomFormatScore
Closes #2731
2023-07-29 06:35:19 +03:00
Bogdan
d73eb1b5f9 Validation for Custom Format specifications
Co-authored-by: Qstick <qstick@gmail.com>
(cherry picked from commit 3d6cf24d7c91f8ff697c34264c249f7450894106)

Closes #2726
2023-07-29 06:32:29 +03:00
Bogdan
39778a95bf Dedupe releases based on indexer priority
(cherry picked from commit 38c717bcef6fa5fcd2ff1c7901639eb888a94a8a)

Closes #2727
2023-07-29 06:28:23 +03:00
Taloth Saldono
9fccca1154 Fixed up some errors and do the guid cache fix on the module instead of backend coz that would cause other issues.
(cherry picked from commit 8eaab46488f00a74197c517c6ef773626aec5173)
2023-07-29 06:27:34 +03:00
Mark McDowall
e165663616 Fixed: Sorting in Interactive search duplicates results
(cherry picked from commit a6637b2911f7818e596c1518e94bd111cff0120b)

Closes #739
Closes #743
2023-07-29 06:27:31 +03:00
Bogdan
b49d2312ab Fixed: Check only enabled Jackett indexers for '/all' endpoint
(cherry picked from commit ae3dd5730e05c5229e7f7092f15c33859524863b)

Closes #2730
2023-07-29 05:38:15 +03:00
Bogdan
52221c7cf4 Fixed: Ensure failing indexers are marked as failed when testing all
(cherry picked from commit b407eba61284d5fb855df6a2868805853aa6f448)

Closes #2735
2023-07-29 05:36:23 +03:00
bakerboy448
ad7b110a0b New: Use better page size for Newznab/Torznab (up to 100) when supported by the indexer
(cherry picked from commit ddb25b109575cc378462a1c3a64705f2003f01f0)

Closes #2181
2023-07-29 05:33:52 +03:00
Weblate
b04b483f86 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ro/
Translation: Servarr/Readarr
2023-07-28 12:55:31 +03:00
Bogdan
b79941e0a1 Fix tests 2023-07-26 14:00:10 +03:00
Weblate
84d47b1f23 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ro/
Translation: Servarr/Readarr
2023-07-26 07:53:53 +03:00
Weblate
17df4d47fb Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: SHUAI.W <x@ousui.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-07-25 01:04:44 +03:00
Bogdan
b9f89dddc9 Bump version to 0.3.0 2023-07-23 09:38:17 +03:00
Weblate
e3fc469cd3 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
Translation: Servarr/Readarr
2023-07-23 05:05:02 +03:00
Bogdan
4304685a65 Add support for deprecated values in field select options
(cherry picked from commit d9786887f3fe30ef60ad9c50b3272bf60dfef309)

Closes #2718
2023-07-23 05:03:40 +03:00
Bogdan
7d77b1fbe5 Trim spaces from a split list in GetValueConverter 2023-07-23 05:02:18 +03:00
Bogdan
1989174801 Fix typo in SkipRedownload
Closes #2711
2023-07-23 05:01:08 +03:00
Bogdan
ac4ae9bb4d Fixed: Ensure Monitoring Options resets to No Change
(cherry picked from commit 180153cd8440df88c9aa5694c67c6cae537dc595)
2023-07-23 04:52:30 +03:00
Bogdan
f399d27470 Cache busting for CSS files
(cherry picked from commit 38f263931ff8faba050762abe5fb692a5bc0d515)
2023-07-23 03:03:51 +03:00
Weblate
c5fd2e3aa0 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translation: Servarr/Readarr
2023-07-21 13:37:45 +03:00
61 changed files with 516 additions and 201 deletions

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests' testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '0.2.4' majorVersion: '0.3.1'
minorVersion: $[counter('minorVersion', 1)] minorVersion: $[counter('minorVersion', 1)]
readarrVersion: '$(majorVersion).$(minorVersion)' readarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(readarrVersion)' buildName: '$(Build.SourceBranchName).$(readarrVersion)'

View File

@@ -391,22 +391,21 @@ then
fi fi
fi fi
if [ "$FRONTEND" = "YES" ]; if [[ "$LINT" = "YES" || "$FRONTEND" = "YES" ]];
then then
YarnInstall YarnInstall
RunWebpack
fi fi
if [ "$LINT" = "YES" ]; if [ "$LINT" = "YES" ];
then then
if [ -z "$FRONTEND" ];
then
YarnInstall
fi
LintUI LintUI
fi fi
if [ "$FRONTEND" = "YES" ];
then
RunWebpack
fi
if [ "$PACKAGES" = "YES" ]; if [ "$PACKAGES" = "YES" ];
then then
UpdateVersionNumber UpdateVersionNumber

View File

@@ -91,7 +91,8 @@ module.exports = (env) => {
}), }),
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
filename: 'Content/styles.css' filename: 'Content/styles.css',
chunkFilename: 'Content/[id]-[chunkhash].css'
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({

View File

@@ -9,7 +9,7 @@ import Link from 'Components/Link/Link';
import { icons } from 'Helpers/Props'; import { icons } from 'Helpers/Props';
import formatDateTime from 'Utilities/Date/formatDateTime'; import formatDateTime from 'Utilities/Date/formatDateTime';
import formatAge from 'Utilities/Number/formatAge'; import formatAge from 'Utilities/Number/formatAge';
import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore'; import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import styles from './HistoryDetails.css'; import styles from './HistoryDetails.css';
@@ -108,7 +108,7 @@ function HistoryDetails(props) {
customFormatScore && customFormatScore !== '0' ? customFormatScore && customFormatScore !== '0' ?
<DescriptionListItem <DescriptionListItem
title={translate('CustomFormatScore')} title={translate('CustomFormatScore')}
data={formatPreferredWordScore(customFormatScore)} data={formatCustomFormatScore(customFormatScore)}
/> : /> :
null null
} }
@@ -225,7 +225,7 @@ function HistoryDetails(props) {
customFormatScore && customFormatScore !== '0' ? customFormatScore && customFormatScore !== '0' ?
<DescriptionListItem <DescriptionListItem
title={translate('CustomFormatScore')} title={translate('CustomFormatScore')}
data={formatPreferredWordScore(customFormatScore)} data={formatCustomFormatScore(customFormatScore)}
/> : /> :
null null
} }
@@ -271,7 +271,7 @@ function HistoryDetails(props) {
customFormatScore && customFormatScore !== '0' ? customFormatScore && customFormatScore !== '0' ?
<DescriptionListItem <DescriptionListItem
title={translate('CustomFormatScore')} title={translate('CustomFormatScore')}
data={formatPreferredWordScore(customFormatScore)} data={formatCustomFormatScore(customFormatScore)}
/> : /> :
null null
} }

View File

@@ -10,7 +10,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow'; import TableRow from 'Components/Table/TableRow';
import Tooltip from 'Components/Tooltip/Tooltip'; import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, tooltipPositions } from 'Helpers/Props'; import { icons, tooltipPositions } from 'Helpers/Props';
import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore'; import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
import HistoryDetailsModal from './Details/HistoryDetailsModal'; import HistoryDetailsModal from './Details/HistoryDetailsModal';
import HistoryEventTypeCell from './HistoryEventTypeCell'; import HistoryEventTypeCell from './HistoryEventTypeCell';
import styles from './HistoryRow.css'; import styles from './HistoryRow.css';
@@ -180,7 +180,7 @@ class HistoryRow extends Component {
className={styles.customFormatScore} className={styles.customFormatScore}
> >
<Tooltip <Tooltip
anchor={formatPreferredWordScore( anchor={formatCustomFormatScore(
customFormatScore, customFormatScore,
customFormats.length customFormats.length
)} )}

View File

@@ -18,7 +18,7 @@ import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds, tooltipPositions } from 'Helpers/Props'; import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal'; import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
import formatBytes from 'Utilities/Number/formatBytes'; import formatBytes from 'Utilities/Number/formatBytes';
import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore'; import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import QueueStatusCell from './QueueStatusCell'; import QueueStatusCell from './QueueStatusCell';
import RemoveQueueItemModal from './RemoveQueueItemModal'; import RemoveQueueItemModal from './RemoveQueueItemModal';
@@ -46,14 +46,14 @@ class QueueRow extends Component {
this.setState({ isRemoveQueueItemModalOpen: true }); this.setState({ isRemoveQueueItemModalOpen: true });
}; };
onRemoveQueueItemModalConfirmed = (blocklist, skipredownload) => { onRemoveQueueItemModalConfirmed = (blocklist, skipRedownload) => {
const { const {
onRemoveQueueItemPress, onRemoveQueueItemPress,
onQueueRowModalOpenOrClose onQueueRowModalOpenOrClose
} = this.props; } = this.props;
onQueueRowModalOpenOrClose(false); onQueueRowModalOpenOrClose(false);
onRemoveQueueItemPress(blocklist, skipredownload); onRemoveQueueItemPress(blocklist, skipRedownload);
this.setState({ isRemoveQueueItemModalOpen: false }); this.setState({ isRemoveQueueItemModalOpen: false });
}; };
@@ -232,7 +232,7 @@ class QueueRow extends Component {
className={styles.customFormatScore} className={styles.customFormatScore}
> >
<Tooltip <Tooltip
anchor={formatPreferredWordScore( anchor={formatCustomFormatScore(
customFormatScore, customFormatScore,
customFormats.length customFormats.length
)} )}

View File

@@ -23,7 +23,7 @@ class RemoveQueueItemModal extends Component {
this.state = { this.state = {
remove: true, remove: true,
blocklist: false, blocklist: false,
skipredownload: false skipRedownload: false
}; };
} }
@@ -34,7 +34,7 @@ class RemoveQueueItemModal extends Component {
this.setState({ this.setState({
remove: true, remove: true,
blocklist: false, blocklist: false,
skipredownload: false skipRedownload: false
}); });
}; };
@@ -49,8 +49,8 @@ class RemoveQueueItemModal extends Component {
this.setState({ blocklist: value }); this.setState({ blocklist: value });
}; };
onSkipReDownloadChange = ({ value }) => { onSkipRedownloadChange = ({ value }) => {
this.setState({ skipredownload: value }); this.setState({ skipRedownload: value });
}; };
onRemoveConfirmed = () => { onRemoveConfirmed = () => {
@@ -76,7 +76,7 @@ class RemoveQueueItemModal extends Component {
isPending isPending
} = this.props; } = this.props;
const { remove, blocklist, skipredownload } = this.state; const { remove, blocklist, skipRedownload } = this.state;
return ( return (
<Modal <Modal
@@ -137,10 +137,10 @@ class RemoveQueueItemModal extends Component {
</FormLabel> </FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="skipredownload" name="skipRedownload"
value={skipredownload} value={skipRedownload}
helpText={translate('SkipredownloadHelpText')} helpText={translate('SkipRedownloadHelpText')}
onChange={this.onSkipReDownloadChange} onChange={this.onSkipRedownloadChange}
/> />
</FormGroup> </FormGroup>
} }

View File

@@ -24,7 +24,7 @@ class RemoveQueueItemsModal extends Component {
this.state = { this.state = {
remove: true, remove: true,
blocklist: false, blocklist: false,
skipredownload: false skipRedownload: false
}; };
} }
@@ -35,7 +35,7 @@ class RemoveQueueItemsModal extends Component {
this.setState({ this.setState({
remove: true, remove: true,
blocklist: false, blocklist: false,
skipredownload: false skipRedownload: false
}); });
}; };
@@ -50,8 +50,8 @@ class RemoveQueueItemsModal extends Component {
this.setState({ blocklist: value }); this.setState({ blocklist: value });
}; };
onSkipReDownloadChange = ({ value }) => { onSkipRedownloadChange = ({ value }) => {
this.setState({ skipredownload: value }); this.setState({ skipRedownload: value });
}; };
onRemoveConfirmed = () => { onRemoveConfirmed = () => {
@@ -77,7 +77,7 @@ class RemoveQueueItemsModal extends Component {
allPending allPending
} = this.props; } = this.props;
const { remove, blocklist, skipredownload } = this.state; const { remove, blocklist, skipRedownload } = this.state;
return ( return (
<Modal <Modal
@@ -138,10 +138,10 @@ class RemoveQueueItemsModal extends Component {
</FormLabel> </FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="skipredownload" name="skipRedownload"
value={skipredownload} value={skipRedownload}
helpText={translate('SkipredownloadHelpText')} helpText={translate('SkipRedownloadHelpText')}
onChange={this.onSkipReDownloadChange} onChange={this.onSkipRedownloadChange}
/> />
</FormGroup> </FormGroup>
} }

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux';
import MoveAuthorModal from 'Author/MoveAuthor/MoveAuthorModal'; import MoveAuthorModal from 'Author/MoveAuthor/MoveAuthorModal';
import MetadataProfileSelectInputConnector from 'Components/Form/MetadataProfileSelectInputConnector'; import MetadataProfileSelectInputConnector from 'Components/Form/MetadataProfileSelectInputConnector';
import MonitorNewItemsSelectInput from 'Components/Form/MonitorNewItemsSelectInput'; import MonitorNewItemsSelectInput from 'Components/Form/MonitorNewItemsSelectInput';
@@ -9,6 +10,7 @@ import SelectInput from 'Components/Form/SelectInput';
import SpinnerButton from 'Components/Link/SpinnerButton'; import SpinnerButton from 'Components/Link/SpinnerButton';
import PageContentFooter from 'Components/Page/PageContentFooter'; import PageContentFooter from 'Components/Page/PageContentFooter';
import { kinds } from 'Helpers/Props'; import { kinds } from 'Helpers/Props';
import { fetchRootFolders } from 'Store/Actions/Settings/rootFolders';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import AuthorEditorFooterLabel from './AuthorEditorFooterLabel'; import AuthorEditorFooterLabel from './AuthorEditorFooterLabel';
import DeleteAuthorModal from './Delete/DeleteAuthorModal'; import DeleteAuthorModal from './Delete/DeleteAuthorModal';
@@ -17,6 +19,10 @@ import styles from './AuthorEditorFooter.css';
const NO_CHANGE = 'noChange'; const NO_CHANGE = 'noChange';
const mapDispatchToProps = {
dispatchFetchRootFolders: fetchRootFolders
};
class AuthorEditorFooter extends Component { class AuthorEditorFooter extends Component {
// //
@@ -39,6 +45,13 @@ class AuthorEditorFooter extends Component {
}; };
} }
//
// Lifecycle
componentDidMount() {
this.props.dispatchFetchRootFolders();
}
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const { const {
isSaving, isSaving,
@@ -341,7 +354,8 @@ AuthorEditorFooter.propTypes = {
showMetadataProfile: PropTypes.bool.isRequired, showMetadataProfile: PropTypes.bool.isRequired,
onSaveSelected: PropTypes.func.isRequired, onSaveSelected: PropTypes.func.isRequired,
onOrganizeAuthorPress: PropTypes.func.isRequired, onOrganizeAuthorPress: PropTypes.func.isRequired,
onRetagAuthorPress: PropTypes.func.isRequired onRetagAuthorPress: PropTypes.func.isRequired,
dispatchFetchRootFolders: PropTypes.func.isRequired
}; };
export default AuthorEditorFooter; export default connect(undefined, mapDispatchToProps)(AuthorEditorFooter);

View File

@@ -17,8 +17,8 @@ function AuthorIndexProgressBar(props) {
detailedProgressBar detailedProgressBar
} = props; } = props;
const progress = bookCount ? bookFileCount / bookCount * 100 : 100; const progress = bookCount ? bookCount / totalBookCount * 100 : 100;
const text = `${bookFileCount} / ${bookCount}`; const text = `${bookCount} / ${totalBookCount}`;
return ( return (
<ProgressBar <ProgressBar

View File

@@ -297,7 +297,7 @@ class AuthorIndexRow extends Component {
progress={progress} progress={progress}
kind={getProgressBarKind(status, monitored, progress)} kind={getProgressBarKind(status, monitored, progress)}
showText={true} showText={true}
text={`${bookFileCount} / ${bookCount}`} text={`${bookCount} / ${totalBookCount}`}
title={translate('BookFileCountBookCountTotalTotalBookCountInterp', [bookFileCount, bookCount, totalBookCount])} title={translate('BookFileCountBookCountTotalTotalBookCountInterp', [bookFileCount, bookCount, totalBookCount])}
width={125} width={125}
/> />

View File

@@ -33,7 +33,7 @@ class MonitoringOptionsModalContent extends Component {
const { const {
isSaving, isSaving,
saveError saveError
} = prevProps; } = this.props;
if (prevProps.isSaving && !isSaving && !saveError) { if (prevProps.isSaving && !isSaving && !saveError) {
this.setState({ this.setState({

View File

@@ -1,6 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Modal from 'Components/Modal/Modal'; import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import BookInteractiveSearchModalContent from './BookInteractiveSearchModalContent'; import BookInteractiveSearchModalContent from './BookInteractiveSearchModalContent';
function BookInteractiveSearchModal(props) { function BookInteractiveSearchModal(props) {
@@ -14,6 +15,7 @@ function BookInteractiveSearchModal(props) {
return ( return (
<Modal <Modal
isOpen={isOpen} isOpen={isOpen}
size={sizes.EXTRA_LARGE}
closeOnBackgroundClick={false} closeOnBackgroundClick={false}
onModalClose={onModalClose} onModalClose={onModalClose}
> >

View File

@@ -30,7 +30,7 @@ class BookshelfFooter extends Component {
const { const {
isSaving, isSaving,
saveError saveError
} = prevProps; } = this.props;
if (prevProps.isSaving && !isSaving && !saveError) { if (prevProps.isSaving && !isSaving && !saveError) {
this.setState({ this.setState({

View File

@@ -13,24 +13,51 @@ class InlineMarkdown extends Component {
data data
} = this.props; } = this.props;
// For now only replace links // For now only replace links or code blocks (not both)
const markdownBlocks = []; const markdownBlocks = [];
if (data) { if (data) {
const regex = RegExp(/\[(.+?)\]\((.+?)\)/g); const linkRegex = RegExp(/\[(.+?)\]\((.+?)\)/g);
let endIndex = 0; let endIndex = 0;
let match = null; let match = null;
while ((match = regex.exec(data)) !== null) {
while ((match = linkRegex.exec(data)) !== null) {
if (match.index > endIndex) { if (match.index > endIndex) {
markdownBlocks.push(data.substr(endIndex, match.index - endIndex)); markdownBlocks.push(data.substr(endIndex, match.index - endIndex));
} }
markdownBlocks.push(<Link key={match.index} to={match[2]}>{match[1]}</Link>); markdownBlocks.push(<Link key={match.index} to={match[2]}>{match[1]}</Link>);
endIndex = match.index + match[0].length; endIndex = match.index + match[0].length;
} }
if (endIndex !== data.length) { if (endIndex !== data.length && markdownBlocks.length > 0) {
markdownBlocks.push(data.substr(endIndex, data.length - endIndex)); markdownBlocks.push(data.substr(endIndex, data.length - endIndex));
} }
const codeRegex = RegExp(/(?=`)`(?!`)[^`]*(?=`)`(?!`)/g);
endIndex = 0;
match = null;
let matchedCode = false;
while ((match = codeRegex.exec(data)) !== null) {
matchedCode = true;
if (match.index > endIndex) {
markdownBlocks.push(data.substr(endIndex, match.index - endIndex));
}
markdownBlocks.push(<code key={`code-${match.index}`}>{match[0].substring(1, match[0].length - 1)}</code>);
endIndex = match.index + match[0].length;
}
if (endIndex !== data.length && markdownBlocks.length > 0 && matchedCode) {
markdownBlocks.push(data.substr(endIndex, data.length - endIndex));
}
if (markdownBlocks.length === 0) {
markdownBlocks.push(data);
}
} }
return <span className={className}>{markdownBlocks}</span>; return <span className={className}>{markdownBlocks}</span>;

View File

@@ -15,7 +15,7 @@ import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import formatDateTime from 'Utilities/Date/formatDateTime'; import formatDateTime from 'Utilities/Date/formatDateTime';
import formatAge from 'Utilities/Number/formatAge'; import formatAge from 'Utilities/Number/formatAge';
import formatBytes from 'Utilities/Number/formatBytes'; import formatBytes from 'Utilities/Number/formatBytes';
import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore'; import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import Peers from './Peers'; import Peers from './Peers';
import styles from './InteractiveSearchRow.css'; import styles from './InteractiveSearchRow.css';
@@ -172,7 +172,7 @@ class InteractiveSearchRow extends Component {
<TableRowCell className={styles.customFormatScore}> <TableRowCell className={styles.customFormatScore}>
<Tooltip <Tooltip
anchor={ anchor={
formatPreferredWordScore(customFormatScore, customFormats.length) formatCustomFormatScore(customFormatScore, customFormats.length)
} }
tooltip={<BookFormats formats={customFormats} />} tooltip={<BookFormats formats={customFormats} />}
position={tooltipPositions.BOTTOM} position={tooltipPositions.BOTTOM}

View File

@@ -371,13 +371,13 @@ export const actionHandlers = handleThunks({
id, id,
remove, remove,
blocklist, blocklist,
skipredownload skipRedownload
} = payload; } = payload;
dispatch(updateItem({ section: paged, id, isRemoving: true })); dispatch(updateItem({ section: paged, id, isRemoving: true }));
const promise = createAjaxRequest({ const promise = createAjaxRequest({
url: `/queue/${id}?removeFromClient=${remove}&blocklist=${blocklist}&skipredownload=${skipredownload}`, url: `/queue/${id}?removeFromClient=${remove}&blocklist=${blocklist}&skipRedownload=${skipRedownload}`,
method: 'DELETE' method: 'DELETE'
}).request; }).request;
@@ -395,7 +395,7 @@ export const actionHandlers = handleThunks({
ids, ids,
remove, remove,
blocklist, blocklist,
skipredownload skipRedownload
} = payload; } = payload;
dispatch(batchActions([ dispatch(batchActions([
@@ -411,7 +411,7 @@ export const actionHandlers = handleThunks({
])); ]));
const promise = createAjaxRequest({ const promise = createAjaxRequest({
url: `/queue/bulk?removeFromClient=${remove}&blocklist=${blocklist}&skipredownload=${skipredownload}`, url: `/queue/bulk?removeFromClient=${remove}&blocklist=${blocklist}&skipRedownload=${skipRedownload}`,
method: 'DELETE', method: 'DELETE',
dataType: 'json', dataType: 'json',
data: JSON.stringify({ ids }) data: JSON.stringify({ ids })

View File

@@ -1,5 +1,7 @@
function formatCustomFormatScore(
function formatPreferredWordScore(input, customFormatsLength = 0) { input?: number,
customFormatsLength = 0
): string {
const score = Number(input); const score = Number(input);
if (score > 0) { if (score > 0) {
@@ -7,10 +9,10 @@ function formatPreferredWordScore(input, customFormatsLength = 0) {
} }
if (score < 0) { if (score < 0) {
return score; return `${score}`;
} }
return customFormatsLength > 0 ? '+0' : ''; return customFormatsLength > 0 ? '+0' : '';
} }
export default formatPreferredWordScore; export default formatCustomFormatScore;

View File

@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Test.Download
{ {
AuthorId = 1, AuthorId = 1,
BookIds = new List<int> { 1 }, BookIds = new List<int> { 1 },
SkipReDownload = true SkipRedownload = true
}; };
Subject.Handle(failedEvent); Subject.Handle(failedEvent);

View File

@@ -38,6 +38,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
_definition = new IndexerDefinition _definition = new IndexerDefinition
{ {
Name = "Indexer", Name = "Indexer",
EnableRss = true,
ConfigContract = "TorznabSettings", ConfigContract = "TorznabSettings",
Settings = torznabSettings Settings = torznabSettings
}; };

View File

@@ -65,12 +65,21 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
} }
[Test] [Test]
public void should_use_pagesize_reported_by_caps() public void should_use_best_pagesize_reported_by_caps()
{ {
_caps.MaxPageSize = 30; _caps.MaxPageSize = 30;
_caps.DefaultPageSize = 25; _caps.DefaultPageSize = 25;
Subject.PageSize.Should().Be(25); Subject.PageSize.Should().Be(30);
}
[Test]
public void should_not_use_pagesize_over_100_even_if_reported_in_caps()
{
_caps.MaxPageSize = 250;
_caps.DefaultPageSize = 25;
Subject.PageSize.Should().Be(100);
} }
[Test] [Test]

View File

@@ -103,12 +103,21 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
} }
[Test] [Test]
public void should_use_pagesize_reported_by_caps() public void should_use_best_pagesize_reported_by_caps()
{ {
_caps.MaxPageSize = 30; _caps.MaxPageSize = 30;
_caps.DefaultPageSize = 25; _caps.DefaultPageSize = 25;
Subject.PageSize.Should().Be(25); Subject.PageSize.Should().Be(30);
}
[Test]
public void should_not_use_pagesize_over_100_even_if_reported_in_caps()
{
_caps.MaxPageSize = 250;
_caps.DefaultPageSize = 25;
Subject.PageSize.Should().Be(100);
} }
[TestCase("http://localhost:9117/", "/api")] [TestCase("http://localhost:9117/", "/api")]

View File

@@ -41,8 +41,8 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
} }
[TestCase("Robert Harris", "Robert Harris")] [TestCase("Robert Harris", "Robert Harris")]
[TestCase("James Patterson", "James Patterson")] [TestCase("Lyndsay Ely", "Lyndsay Ely")]
[TestCase("Antoine de Saint-Exupéry", "Antoine de Saint-Exupéry")] [TestCase("Elisa Puricelli Guerra", "Elisa Puricelli Guerra")]
public void successful_author_search(string title, string expected) public void successful_author_search(string title, string expected)
{ {
var result = Subject.SearchForNewAuthor(title); var result = Subject.SearchForNewAuthor(title);
@@ -54,7 +54,7 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
ExceptionVerification.IgnoreWarns(); ExceptionVerification.IgnoreWarns();
} }
[TestCase("Harry Potter and the sorcerer's stone", null, "Harry Potter and the Sorcerer's Stone")] [TestCase("Harry Potter and the sorcerer's stone a summary of the novel", null, "Harry Potter and the Sorcerer's Stone (Book 1): A Summary Of The Novel")]
[TestCase("edition:3", null, "Harry Potter and the Sorcerer's Stone")] [TestCase("edition:3", null, "Harry Potter and the Sorcerer's Stone")]
[TestCase("edition: 3", null, "Harry Potter and the Sorcerer's Stone")] [TestCase("edition: 3", null, "Harry Potter and the Sorcerer's Stone")]
[TestCase("asin:B0192CTMYG", null, "Harry Potter and the Sorcerer's Stone")] [TestCase("asin:B0192CTMYG", null, "Harry Potter and the Sorcerer's Stone")]
@@ -85,8 +85,8 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
ExceptionVerification.IgnoreWarns(); ExceptionVerification.IgnoreWarns();
} }
[TestCase("Philip Pullman", 0, typeof(Author), new[] { "Philip Pullman" }, TestName = "author")] [TestCase("Catherine Butler", 0, typeof(Author), new[] { "Catherine Butler" }, TestName = "author")]
[TestCase("Philip Pullman", 1, typeof(Book), new[] { "Northern Lights", "The Amber Spyglass" }, TestName = "book")] [TestCase("Catherine Butler", 1, typeof(Book), new[] { "Twisted Winter", "Shattered Dreams" }, TestName = "book")]
public void successful_combined_search(string query, int position, Type resultType, string[] expected) public void successful_combined_search(string query, int position, Type resultType, string[] expected)
{ {
var result = Subject.SearchForNewEntity(query); var result = Subject.SearchForNewEntity(query);
@@ -97,7 +97,7 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
{ {
var cast = result[position] as Author; var cast = result[position] as Author;
cast.Should().NotBeNull(); cast.Should().NotBeNull();
cast.Name.Should().ContainAll(expected); cast.Name.Should().ContainAny(expected);
} }
else else
{ {

View File

@@ -25,8 +25,8 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
} }
[TestCase("Robert Harris", 575)] [TestCase("Robert Harris", 575)]
[TestCase("James Patterson", 3780)] [TestCase("Lyndsay Ely", 8056539)]
[TestCase("Antoine de Saint-Exupéry", 1020792)] [TestCase("Elisa Puricelli Guerra", 4481805)]
public void successful_author_search(string title, int expected) public void successful_author_search(string title, int expected)
{ {
var result = Subject.Search(title); var result = Subject.Search(title);
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
ExceptionVerification.IgnoreWarns(); ExceptionVerification.IgnoreWarns();
} }
[TestCase("Harry Potter and the sorcerer's stone", 3)] [TestCase("Harry Potter and the sorcerer's stone a summary of the novel", 23314781)]
[TestCase("B0192CTMYG", 61209488)] [TestCase("B0192CTMYG", 61209488)]
[TestCase("9780439554930", 48517161)] [TestCase("9780439554930", 48517161)]
public void successful_book_search(string title, int expected) public void successful_book_search(string title, int expected)

View File

@@ -0,0 +1,28 @@
using FluentValidation;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.AutoTagging.Specifications
{
public class MonitoredSpecificationValidator : AbstractValidator<MonitoredSpecification>
{
}
public class MonitoredSpecification : AutoTaggingSpecificationBase
{
private static readonly MonitoredSpecificationValidator Validator = new ();
public override int Order => 1;
public override string ImplementationName => "Monitored";
protected override bool IsSatisfiedByWithoutNegate(Series series)
{
return series.Monitored;
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(036)]
public class add_result_to_commands : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Commands").AddColumn("Result").AsInt32().WithDefaultValue(1);
}
}
}

View File

@@ -21,6 +21,6 @@ namespace NzbDrone.Core.Download
public string Message { get; set; } public string Message { get; set; }
public Dictionary<string, string> Data { get; set; } public Dictionary<string, string> Data { get; set; }
public TrackedDownload TrackedDownload { get; set; } public TrackedDownload TrackedDownload { get; set; }
public bool SkipReDownload { get; set; } public bool SkipRedownload { get; set; }
} }
} }

View File

@@ -9,8 +9,8 @@ namespace NzbDrone.Core.Download
{ {
public interface IFailedDownloadService public interface IFailedDownloadService
{ {
void MarkAsFailed(int historyId, bool skipReDownload = false); void MarkAsFailed(int historyId, bool skipRedownload = false);
void MarkAsFailed(string downloadId, bool skipReDownload = false); void MarkAsFailed(string downloadId, bool skipRedownload = false);
void Check(TrackedDownload trackedDownload); void Check(TrackedDownload trackedDownload);
void ProcessFailed(TrackedDownload trackedDownload); void ProcessFailed(TrackedDownload trackedDownload);
} }
@@ -30,14 +30,14 @@ namespace NzbDrone.Core.Download
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
} }
public void MarkAsFailed(int historyId, bool skipReDownload = false) public void MarkAsFailed(int historyId, bool skipRedownload = false)
{ {
var history = _historyService.Get(historyId); var history = _historyService.Get(historyId);
var downloadId = history.DownloadId; var downloadId = history.DownloadId;
if (downloadId.IsNullOrWhiteSpace()) if (downloadId.IsNullOrWhiteSpace())
{ {
PublishDownloadFailedEvent(new List<EntityHistory> { history }, "Manually marked as failed", skipReDownload: skipReDownload); PublishDownloadFailedEvent(new List<EntityHistory> { history }, "Manually marked as failed", skipRedownload: skipRedownload);
} }
else else
{ {
@@ -46,7 +46,7 @@ namespace NzbDrone.Core.Download
} }
} }
public void MarkAsFailed(string downloadId, bool skipReDownload = false) public void MarkAsFailed(string downloadId, bool skipRedownload = false)
{ {
var history = _historyService.Find(downloadId, EntityHistoryEventType.Grabbed); var history = _historyService.Find(downloadId, EntityHistoryEventType.Grabbed);
@@ -54,7 +54,7 @@ namespace NzbDrone.Core.Download
{ {
var trackedDownload = _trackedDownloadService.Find(downloadId); var trackedDownload = _trackedDownloadService.Find(downloadId);
PublishDownloadFailedEvent(history, "Manually marked as failed", trackedDownload, skipReDownload); PublishDownloadFailedEvent(history, "Manually marked as failed", trackedDownload, skipRedownload);
} }
} }
@@ -114,7 +114,7 @@ namespace NzbDrone.Core.Download
PublishDownloadFailedEvent(grabbedItems, failure, trackedDownload); PublishDownloadFailedEvent(grabbedItems, failure, trackedDownload);
} }
private void PublishDownloadFailedEvent(List<EntityHistory> historyItems, string message, TrackedDownload trackedDownload = null, bool skipReDownload = false) private void PublishDownloadFailedEvent(List<EntityHistory> historyItems, string message, TrackedDownload trackedDownload = null, bool skipRedownload = false)
{ {
var historyItem = historyItems.First(); var historyItem = historyItems.First();
@@ -129,7 +129,7 @@ namespace NzbDrone.Core.Download
Message = message, Message = message,
Data = historyItem.Data, Data = historyItem.Data,
TrackedDownload = trackedDownload, TrackedDownload = trackedDownload,
SkipReDownload = skipReDownload SkipRedownload = skipRedownload
}; };
_eventAggregator.PublishEvent(downloadFailedEvent); _eventAggregator.PublishEvent(downloadFailedEvent);

View File

@@ -29,7 +29,7 @@ namespace NzbDrone.Core.Download
[EventHandleOrder(EventHandleOrder.Last)] [EventHandleOrder(EventHandleOrder.Last)]
public void Handle(DownloadFailedEvent message) public void Handle(DownloadFailedEvent message)
{ {
if (message.SkipReDownload) if (message.SkipRedownload)
{ {
_logger.Debug("Skip redownloading requested by user"); _logger.Debug("Skip redownloading requested by user");
return; return;

View File

@@ -1,4 +1,5 @@
using System.Net; using System;
using System.Net;
using NzbDrone.Common.Exceptions; using NzbDrone.Common.Exceptions;
namespace NzbDrone.Core.Exceptions namespace NzbDrone.Core.Exceptions
@@ -13,6 +14,12 @@ namespace NzbDrone.Core.Exceptions
StatusCode = statusCode; StatusCode = statusCode;
} }
public NzbDroneClientException(HttpStatusCode statusCode, string message, Exception innerException, params object[] args)
: base(message, innerException, args)
{
StatusCode = statusCode;
}
public NzbDroneClientException(HttpStatusCode statusCode, string message) public NzbDroneClientException(HttpStatusCode statusCode, string message)
: base(message) : base(message)
{ {

View File

@@ -8,6 +8,7 @@ using NzbDrone.Core.ThingiProvider.Events;
namespace NzbDrone.Core.HealthCheck.Checks namespace NzbDrone.Core.HealthCheck.Checks
{ {
[CheckOn(typeof(ProviderAddedEvent<IIndexer>))]
[CheckOn(typeof(ProviderUpdatedEvent<IIndexer>))] [CheckOn(typeof(ProviderUpdatedEvent<IIndexer>))]
[CheckOn(typeof(ProviderDeletedEvent<IIndexer>))] [CheckOn(typeof(ProviderDeletedEvent<IIndexer>))]
[CheckOn(typeof(ProviderStatusChangedEvent<IIndexer>))] [CheckOn(typeof(ProviderStatusChangedEvent<IIndexer>))]
@@ -23,12 +24,15 @@ namespace NzbDrone.Core.HealthCheck.Checks
public override HealthCheck Check() public override HealthCheck Check()
{ {
var jackettAllProviders = _providerFactory.All().Where( var jackettAllProviders = _providerFactory.All()
i => i.ConfigContract.Equals("TorznabSettings") && .Where(
((i.Settings as TorznabSettings).BaseUrl.Contains("/torznab/all/api", StringComparison.InvariantCultureIgnoreCase) || i => i.Enable &&
(i.Settings as TorznabSettings).BaseUrl.Contains("/api/v2.0/indexers/all/results/torznab", StringComparison.InvariantCultureIgnoreCase) || i.ConfigContract.Equals("TorznabSettings") &&
(i.Settings as TorznabSettings).ApiPath.Contains("/torznab/all/api", StringComparison.InvariantCultureIgnoreCase) || (((TorznabSettings)i.Settings).BaseUrl.Contains("/torznab/all/api", StringComparison.InvariantCultureIgnoreCase) ||
(i.Settings as TorznabSettings).ApiPath.Contains("/api/v2.0/indexers/all/results/torznab", StringComparison.InvariantCultureIgnoreCase))); ((TorznabSettings)i.Settings).BaseUrl.Contains("/api/v2.0/indexers/all/results/torznab", StringComparison.InvariantCultureIgnoreCase) ||
((TorznabSettings)i.Settings).ApiPath.Contains("/torznab/all/api", StringComparison.InvariantCultureIgnoreCase) ||
((TorznabSettings)i.Settings).ApiPath.Contains("/api/v2.0/indexers/all/results/torznab", StringComparison.InvariantCultureIgnoreCase)))
.ToArray();
if (jackettAllProviders.Empty()) if (jackettAllProviders.Empty())
{ {
@@ -37,8 +41,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
return new HealthCheck(GetType(), return new HealthCheck(GetType(),
HealthCheckResult.Warning, HealthCheckResult.Warning,
string.Format(_localizationService.GetLocalizedString("IndexerJackettAll"), string.Format(_localizationService.GetLocalizedString("IndexerJackettAll"), string.Join(", ", jackettAllProviders.Select(i => i.Name))),
string.Join(", ", jackettAllProviders.Select(i => i.Name))),
"#jackett-all-endpoint-used"); "#jackett-all-endpoint-used");
} }
} }

View File

@@ -43,14 +43,26 @@ namespace NzbDrone.Core.IndexerSearch
public List<DownloadDecision> BookSearch(int bookId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch) public List<DownloadDecision> BookSearch(int bookId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch)
{ {
var downloadDecisions = new List<DownloadDecision>();
var book = _bookService.GetBook(bookId); var book = _bookService.GetBook(bookId);
return BookSearch(book, missingOnly, userInvokedSearch, interactiveSearch);
var decisions = BookSearch(book, missingOnly, userInvokedSearch, interactiveSearch);
downloadDecisions.AddRange(decisions);
return DeDupeDecisions(downloadDecisions);
} }
public List<DownloadDecision> AuthorSearch(int authorId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch) public List<DownloadDecision> AuthorSearch(int authorId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch)
{ {
var downloadDecisions = new List<DownloadDecision>();
var author = _authorService.GetAuthor(authorId); var author = _authorService.GetAuthor(authorId);
return AuthorSearch(author, missingOnly, userInvokedSearch, interactiveSearch);
var decisions = AuthorSearch(author, missingOnly, userInvokedSearch, interactiveSearch);
downloadDecisions.AddRange(decisions);
return DeDupeDecisions(downloadDecisions);
} }
public List<DownloadDecision> AuthorSearch(Author author, bool missingOnly, bool userInvokedSearch, bool interactiveSearch) public List<DownloadDecision> AuthorSearch(Author author, bool missingOnly, bool userInvokedSearch, bool interactiveSearch)
@@ -150,5 +162,13 @@ namespace NzbDrone.Core.IndexerSearch
return _makeDownloadDecision.GetSearchDecision(reports, criteriaBase).ToList(); return _makeDownloadDecision.GetSearchDecision(reports, criteriaBase).ToList();
} }
private List<DownloadDecision> DeDupeDecisions(List<DownloadDecision> decisions)
{
// De-dupe reports by guid so duplicate results aren't returned. Pick the one with the least rejections and higher indexer priority.
return decisions.GroupBy(d => d.RemoteBook.Release.Guid)
.Select(d => d.OrderBy(v => v.Rejections.Count()).ThenBy(v => v.RemoteBook?.Release?.IndexerPriority ?? IndexerDefinition.DefaultPriority).First())
.ToList();
}
} }
} }

View File

@@ -79,7 +79,6 @@ namespace NzbDrone.Core.Indexers
result.ForEach(c => result.ForEach(c =>
{ {
c.Guid = string.Concat(Definition.Id, "_", c.Guid);
c.IndexerId = Definition.Id; c.IndexerId = Definition.Id;
c.Indexer = Definition.Name; c.Indexer = Definition.Name;
c.DownloadProtocol = Protocol; c.DownloadProtocol = Protocol;

View File

@@ -4,6 +4,13 @@ namespace NzbDrone.Core.Indexers
{ {
public class IndexerDefinition : ProviderDefinition public class IndexerDefinition : ProviderDefinition
{ {
public const int DefaultPriority = 25;
public IndexerDefinition()
{
Priority = DefaultPriority;
}
public bool EnableRss { get; set; } public bool EnableRss { get; set; }
public bool EnableAutomaticSearch { get; set; } public bool EnableAutomaticSearch { get; set; }
public bool EnableInteractiveSearch { get; set; } public bool EnableInteractiveSearch { get; set; }
@@ -11,7 +18,7 @@ namespace NzbDrone.Core.Indexers
public DownloadProtocol Protocol { get; set; } public DownloadProtocol Protocol { get; set; }
public bool SupportsRss { get; set; } public bool SupportsRss { get; set; }
public bool SupportsSearch { get; set; } public bool SupportsSearch { get; set; }
public int Priority { get; set; } = 25; public int Priority { get; set; }
public override bool Enable => EnableRss || EnableAutomaticSearch || EnableInteractiveSearch; public override bool Enable => EnableRss || EnableAutomaticSearch || EnableInteractiveSearch;

View File

@@ -102,10 +102,19 @@ namespace NzbDrone.Core.Indexers
{ {
var result = base.Test(definition); var result = base.Test(definition);
if ((result == null || result.IsValid) && definition.Id != 0) if (definition.Id == 0)
{
return result;
}
if (result == null || result.IsValid)
{ {
_indexerStatusService.RecordSuccess(definition.Id); _indexerStatusService.RecordSuccess(definition.Id);
} }
else
{
_indexerStatusService.RecordFailure(definition.Id);
}
return result; return result;
} }

View File

@@ -19,8 +19,13 @@ namespace NzbDrone.Core.Indexers.Newznab
public override string Name => "Newznab"; public override string Name => "Newznab";
public override DownloadProtocol Protocol => DownloadProtocol.Usenet; public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
public override int PageSize => GetProviderPageSize();
public override int PageSize => _capabilitiesProvider.GetCapabilities(Settings).DefaultPageSize; public Newznab(INewznabCapabilitiesProvider capabilitiesProvider, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, indexerStatusService, configService, parsingService, logger)
{
_capabilitiesProvider = capabilitiesProvider;
}
public override IIndexerRequestGenerator GetRequestGenerator() public override IIndexerRequestGenerator GetRequestGenerator()
{ {
@@ -54,12 +59,6 @@ namespace NzbDrone.Core.Indexers.Newznab
} }
} }
public Newznab(INewznabCapabilitiesProvider capabilitiesProvider, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, indexerStatusService, configService, parsingService, logger)
{
_capabilitiesProvider = capabilitiesProvider;
}
private IndexerDefinition GetDefinition(string name, NewznabSettings settings) private IndexerDefinition GetDefinition(string name, NewznabSettings settings)
{ {
return new IndexerDefinition return new IndexerDefinition
@@ -163,5 +162,17 @@ namespace NzbDrone.Core.Indexers.Newznab
return base.RequestAction(action, query); return base.RequestAction(action, query);
} }
private int GetProviderPageSize()
{
try
{
return Math.Min(100, Math.Max(_capabilitiesProvider.GetCapabilities(Settings).DefaultPageSize, _capabilitiesProvider.GetCapabilities(Settings).MaxPageSize));
}
catch
{
return 100;
}
}
} }
} }

View File

@@ -19,7 +19,13 @@ namespace NzbDrone.Core.Indexers.Torznab
public override string Name => "Torznab"; public override string Name => "Torznab";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent; public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override int PageSize => _capabilitiesProvider.GetCapabilities(Settings).DefaultPageSize; public override int PageSize => GetProviderPageSize();
public Torznab(INewznabCapabilitiesProvider capabilitiesProvider, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, indexerStatusService, configService, parsingService, logger)
{
_capabilitiesProvider = capabilitiesProvider;
}
public override IIndexerRequestGenerator GetRequestGenerator() public override IIndexerRequestGenerator GetRequestGenerator()
{ {
@@ -35,12 +41,6 @@ namespace NzbDrone.Core.Indexers.Torznab
return new TorznabRssParser(); return new TorznabRssParser();
} }
public Torznab(INewznabCapabilitiesProvider capabilitiesProvider, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, indexerStatusService, configService, parsingService, logger)
{
_capabilitiesProvider = capabilitiesProvider;
}
private IndexerDefinition GetDefinition(string name, TorznabSettings settings) private IndexerDefinition GetDefinition(string name, TorznabSettings settings)
{ {
return new IndexerDefinition return new IndexerDefinition
@@ -147,5 +147,17 @@ namespace NzbDrone.Core.Indexers.Torznab
return base.RequestAction(action, query); return base.RequestAction(action, query);
} }
private int GetProviderPageSize()
{
try
{
return Math.Min(100, Math.Max(_capabilitiesProvider.GetCapabilities(Settings).DefaultPageSize, _capabilitiesProvider.GetCapabilities(Settings).MaxPageSize));
}
catch
{
return 100;
}
}
} }
} }

View File

@@ -756,7 +756,6 @@
"ReadarrSupportsMultipleListsForImportingBooksAndAuthorsIntoTheDatabase": "Lidarr unterstützt mehrere Listen für den Import von Alben und Künstlern in die Datenbank.", "ReadarrSupportsMultipleListsForImportingBooksAndAuthorsIntoTheDatabase": "Lidarr unterstützt mehrere Listen für den Import von Alben und Künstlern in die Datenbank.",
"TotalBookCountBooksTotalBookFileCountBooksWithFilesInterp": "{0} Titel insgesamt. {1} Titel mit Dateien.", "TotalBookCountBooksTotalBookFileCountBooksWithFilesInterp": "{0} Titel insgesamt. {1} Titel mit Dateien.",
"SearchFiltered": "Suche gefilterte", "SearchFiltered": "Suche gefilterte",
"SkipredownloadHelpText": "Verhindert, dass Lidarr versucht alternative Veröffentlichungen für die entfernten Objekte herunterzuladen",
"AddList": "Liste hinzufügen", "AddList": "Liste hinzufügen",
"InstanceName": "Instanzname", "InstanceName": "Instanzname",
"InstanceNameHelpText": "Instanzname im Browser-Tab und für Syslog-Anwendungsname", "InstanceNameHelpText": "Instanzname im Browser-Tab und für Syslog-Anwendungsname",

View File

@@ -614,7 +614,6 @@
"StatusEndedDeceased": "Αποθανών", "StatusEndedDeceased": "Αποθανών",
"TrackTitle": "Τίτλος κομματιού", "TrackTitle": "Τίτλος κομματιού",
"SkipRedownload": "Παράλειψη επανάληψης λήψης", "SkipRedownload": "Παράλειψη επανάληψης λήψης",
"SkipredownloadHelpText": "Αποτρέπει το Readarr από το να δοκιμάσει τη λήψη εναλλακτικών εκδόσεων για τα αφαιρεμένα στοιχεία",
"CollapseMultipleBooks": "Σύμπτυξη πολλών βιβλίων", "CollapseMultipleBooks": "Σύμπτυξη πολλών βιβλίων",
"ConvertToFormat": "Μετατροπή σε Μορφοποίηση", "ConvertToFormat": "Μετατροπή σε Μορφοποίηση",
"DataNewBooks": "Παρακολουθήστε τα νέα βιβλία που κυκλοφόρησαν μετά το νεότερο υπάρχον βιβλίο", "DataNewBooks": "Παρακολουθήστε τα νέα βιβλία που κυκλοφόρησαν μετά το νεότερο υπάρχον βιβλίο",

View File

@@ -825,8 +825,8 @@
"SkipFreeSpaceCheckWhenImportingHelpText": "Use when Readarr is unable to detect free space from your author root folder", "SkipFreeSpaceCheckWhenImportingHelpText": "Use when Readarr is unable to detect free space from your author root folder",
"SkipPartBooksAndSets": "Skip part books and sets", "SkipPartBooksAndSets": "Skip part books and sets",
"SkipRedownload": "Skip Redownload", "SkipRedownload": "Skip Redownload",
"SkipRedownloadHelpText": "Prevents Readarr from trying download alternative releases for the removed items",
"SkipSecondarySeriesBooks": "Skip secondary series books", "SkipSecondarySeriesBooks": "Skip secondary series books",
"SkipredownloadHelpText": "Prevents Readarr from trying download alternative releases for the removed items",
"SmartReplace": "Smart Replace", "SmartReplace": "Smart Replace",
"SorryThatAuthorCannotBeFound": "Sorry, that author cannot be found.", "SorryThatAuthorCannotBeFound": "Sorry, that author cannot be found.",
"SorryThatBookCannotBeFound": "Sorry, that book cannot be found.", "SorryThatBookCannotBeFound": "Sorry, that book cannot be found.",

View File

@@ -761,7 +761,6 @@
"ContinuingNoAdditionalBooksAreExpected": "Abumeita ei odoteta lisää", "ContinuingNoAdditionalBooksAreExpected": "Abumeita ei odoteta lisää",
"MusicBrainzBookID": "MusicBrainz-kappaletunniste", "MusicBrainzBookID": "MusicBrainz-kappaletunniste",
"SearchForAllCutoffUnmetBooks": "Etsi kaikkia albumeita, joiden katkaisutasoa ei ole savutettu", "SearchForAllCutoffUnmetBooks": "Etsi kaikkia albumeita, joiden katkaisutasoa ei ole savutettu",
"SkipredownloadHelpText": "Estää poistettujen kohteiden vaihtoehtoisten julkaisujen latauksen.",
"SearchForMonitoredBooks": "Etsi valvottuja albumeita", "SearchForMonitoredBooks": "Etsi valvottuja albumeita",
"DataExistingBooks": "Valvo albumeita, joille on tiedostoja tai joita ei ole vielä julkaistu.", "DataExistingBooks": "Valvo albumeita, joille on tiedostoja tai joita ei ole vielä julkaistu.",
"DataMissingBooks": "Seuraa albumeita, joille ei ole tiedostoja tai joita ei ole vielä julkaistu.", "DataMissingBooks": "Seuraa albumeita, joille ei ole tiedostoja tai joita ei ole vielä julkaistu.",

View File

@@ -597,7 +597,6 @@
"StatusEndedDeceased": "Elhunyt", "StatusEndedDeceased": "Elhunyt",
"StatusEndedContinuing": "Folytatás", "StatusEndedContinuing": "Folytatás",
"SpecificBook": "Konkrét könyv", "SpecificBook": "Konkrét könyv",
"SkipredownloadHelpText": "Megakadályozza, hogy a Readarr megpróbálja letölteni az eltávolított elemek alternatív kiadásait",
"SkipSecondarySeriesBooks": "A másodlagos sorozatú könyvek kihagyása", "SkipSecondarySeriesBooks": "A másodlagos sorozatú könyvek kihagyása",
"SkipRedownload": "Az újraletöltés átugrása", "SkipRedownload": "Az újraletöltés átugrása",
"SkipPartBooksAndSets": "Részletkönyvek és könyvkészlet(ek) kihagyása", "SkipPartBooksAndSets": "Részletkönyvek és könyvkészlet(ek) kihagyása",

View File

@@ -473,7 +473,6 @@
"SearchBoxPlaceHolder": "P. ex. Guerra e Paz, goodreads:656, isbn:067003469X, asin:B00JCDK5ME", "SearchBoxPlaceHolder": "P. ex. Guerra e Paz, goodreads:656, isbn:067003469X, asin:B00JCDK5ME",
"ShouldSearchHelpText": "Pesquisar indexadores para novos itens adicionados. Utilize com cuidado para listas grandes.", "ShouldSearchHelpText": "Pesquisar indexadores para novos itens adicionados. Utilize com cuidado para listas grandes.",
"SkipPartBooksAndSets": "Ignorar livros e conjuntos incompletos", "SkipPartBooksAndSets": "Ignorar livros e conjuntos incompletos",
"SkipredownloadHelpText": "Impede que o Readarr tente transferir versões alternativas para itens removidos",
"ASIN": "ASIN", "ASIN": "ASIN",
"Continuing": "Continuação", "Continuing": "Continuação",
"ConsoleLogLevel": "Nível de registo do console", "ConsoleLogLevel": "Nível de registo do console",

View File

@@ -17,13 +17,13 @@
"AgeWhenGrabbed": "Tempo de vida (quando obtido)", "AgeWhenGrabbed": "Tempo de vida (quando obtido)",
"ApiKeyHelpTextWarning": "Requer reinício para ter efeito", "ApiKeyHelpTextWarning": "Requer reinício para ter efeito",
"LoadingBooksFailed": "Falha ao carregar livros", "LoadingBooksFailed": "Falha ao carregar livros",
"Logs": "Registros", "Logs": "Logs",
"MustContain": "Deve conter", "MustContain": "Deve conter",
"ProxyPasswordHelpText": "Você só precisa inserir um nome de usuário e uma senha se solicitado. Caso contrário, deixe em branco.", "ProxyPasswordHelpText": "Você só precisa inserir um nome de usuário e uma senha se solicitado. Caso contrário, deixe em branco.",
"SslCertPathHelpTextWarning": "Requer reinício para ter efeito", "SslCertPathHelpTextWarning": "Requer reinício para ter efeito",
"UnableToLoadMetadataProfiles": "Não foi possível carregar os perfis de metadados", "UnableToLoadMetadataProfiles": "Não foi possível carregar os perfis de metadados",
"AddListExclusion": "Adicionar exclusão à lista", "AddListExclusion": "Adicionar exclusão à lista",
"AddingTag": "Adicionando etiqueta", "AddingTag": "Adicionar tag",
"AlreadyInYourLibrary": "Já está na sua biblioteca", "AlreadyInYourLibrary": "Já está na sua biblioteca",
"AlternateTitles": "Títulos alternativos", "AlternateTitles": "Títulos alternativos",
"Analytics": "Análises", "Analytics": "Análises",
@@ -37,14 +37,14 @@
"AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Livros excluídos do disco deixam de ser monitorados no Readarr automaticamente", "AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Livros excluídos do disco deixam de ser monitorados no Readarr automaticamente",
"Automatic": "Automático", "Automatic": "Automático",
"BackupFolderHelpText": "Os caminhos relativos estarão no diretório AppData do Readarr", "BackupFolderHelpText": "Os caminhos relativos estarão no diretório AppData do Readarr",
"BackupNow": "Fazer Backup", "BackupNow": "Fazer Backup Agora",
"BackupRetentionHelpText": "Backups automáticos anteriores ao período de retenção serão limpos automaticamente", "BackupRetentionHelpText": "Backups automáticos anteriores ao período de retenção serão limpos automaticamente",
"Backups": "Backups", "Backups": "Backups",
"BindAddress": "Fixar Endereço", "BindAddress": "Fixar Endereço",
"BindAddressHelpText": "Endereço IP válido, localhost ou '*' para todas as interfaces", "BindAddressHelpText": "Endereço IP válido, localhost ou '*' para todas as interfaces",
"BindAddressHelpTextWarning": "Requer reiniciar para ter efeito", "BindAddressHelpTextWarning": "Requer reiniciar para ter efeito",
"BookIsDownloading": "O livro está baixando", "BookIsDownloading": "O livro está baixando",
"DiskSpace": "Espaço em disco", "DiskSpace": "Espaço em Disco",
"Docker": "Docker", "Docker": "Docker",
"DownloadClient": "Cliente de download", "DownloadClient": "Cliente de download",
"DownloadClientSettings": "Configurações do cliente de download", "DownloadClientSettings": "Configurações do cliente de download",
@@ -107,7 +107,7 @@
"DelayProfiles": "Perfis de atraso", "DelayProfiles": "Perfis de atraso",
"DelayingDownloadUntilInterp": "Atrasando o download até {0} às {1}", "DelayingDownloadUntilInterp": "Atrasando o download até {0} às {1}",
"Delete": "Excluir", "Delete": "Excluir",
"DeleteBackup": "Excluir backup", "DeleteBackup": "Excluir Backup",
"DeleteBackupMessageText": "Tem certeza que deseja excluir o backup \"{0}\"?", "DeleteBackupMessageText": "Tem certeza que deseja excluir o backup \"{0}\"?",
"DeleteDelayProfile": "Excluir perfil de atraso", "DeleteDelayProfile": "Excluir perfil de atraso",
"DeleteDelayProfileMessageText": "Tem certeza de que deseja excluir este perfil de atraso?", "DeleteDelayProfileMessageText": "Tem certeza de que deseja excluir este perfil de atraso?",
@@ -163,7 +163,7 @@
"ForMoreInformationOnTheIndividualDownloadClientsClickOnTheInfoButtons": "Para obter mais informações sobre os clientes de download individuais, clique nos botões de informações.", "ForMoreInformationOnTheIndividualDownloadClientsClickOnTheInfoButtons": "Para obter mais informações sobre os clientes de download individuais, clique nos botões de informações.",
"ForMoreInformationOnTheIndividualIndexersClickOnTheInfoButtons": "Para saber mais sobre cada indexador, clique nos botões de informações.", "ForMoreInformationOnTheIndividualIndexersClickOnTheInfoButtons": "Para saber mais sobre cada indexador, clique nos botões de informações.",
"ForMoreInformationOnTheIndividualListsClickOnTheInfoButtons": "Para saber mais sobre cada lista, clique nos botões de informação.", "ForMoreInformationOnTheIndividualListsClickOnTheInfoButtons": "Para saber mais sobre cada lista, clique nos botões de informação.",
"GeneralSettings": "Configurações gerais", "GeneralSettings": "Configurações Gerais",
"Global": "Global", "Global": "Global",
"GoToInterp": "Ir para {0}", "GoToInterp": "Ir para {0}",
"Grab": "Obter", "Grab": "Obter",
@@ -225,11 +225,11 @@
"New": "Novo", "New": "Novo",
"NoBackupsAreAvailable": "Não há backups disponíveis", "NoBackupsAreAvailable": "Não há backups disponíveis",
"NoHistory": "Sem histórico.", "NoHistory": "Sem histórico.",
"NoLeaveIt": "Não, deixe assim", "NoLeaveIt": "Não, deixe-o",
"NoLimitForAnyRuntime": "Sem limite para qualquer tempo de execução", "NoLimitForAnyRuntime": "Sem limite para qualquer tempo de execução",
"NoLogFiles": "Sem arquivos de log", "NoLogFiles": "Nenhum arquivo de log",
"NoMinimumForAnyRuntime": "Sem mínimo para qualquer tempo de execução", "NoMinimumForAnyRuntime": "Sem mínimo para qualquer tempo de execução",
"NoUpdatesAreAvailable": "Não há atualizações disponíveis", "NoUpdatesAreAvailable": "Nenhuma atualização está disponível",
"None": "Nenhum", "None": "Nenhum",
"NotificationTriggers": "Acionadores da notificação", "NotificationTriggers": "Acionadores da notificação",
"OnGrabHelpText": "Ao obter", "OnGrabHelpText": "Ao obter",
@@ -269,7 +269,7 @@
"Queue": "Fila", "Queue": "Fila",
"RSSSync": "Sincronização RSS", "RSSSync": "Sincronização RSS",
"RSSSyncInterval": "Intervalo da sincronização RSS", "RSSSyncInterval": "Intervalo da sincronização RSS",
"ReadTheWikiForMoreInformation": "Leia a Wiki para saber mais", "ReadTheWikiForMoreInformation": "Leia o Wiki para mais informações",
"ReadarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "O Readarr oferece suporte a qualquer indexador que usa o padrão Newznab, além de outros indexadores, listados abaixo.", "ReadarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "O Readarr oferece suporte a qualquer indexador que usa o padrão Newznab, além de outros indexadores, listados abaixo.",
"ReadarrTags": "Tags do Readarr", "ReadarrTags": "Tags do Readarr",
"Real": "Real", "Real": "Real",
@@ -363,7 +363,7 @@
"StandardBookFormat": "Formato de livro padrão", "StandardBookFormat": "Formato de livro padrão",
"StartTypingOrSelectAPathBelow": "Comece a digitar ou selecione um caminho abaixo", "StartTypingOrSelectAPathBelow": "Comece a digitar ou selecione um caminho abaixo",
"StartupDirectory": "Diretório de inicialização", "StartupDirectory": "Diretório de inicialização",
"Status": "Status", "Status": "Estado",
"StatusEndedEnded": "Terminado", "StatusEndedEnded": "Terminado",
"Style": "Estilo", "Style": "Estilo",
"SuccessMyWorkIsDoneNoFilesToRename": "Êba, já terminei! Não há arquivos a renomear.", "SuccessMyWorkIsDoneNoFilesToRename": "Êba, já terminei! Não há arquivos a renomear.",
@@ -375,7 +375,7 @@
"TagIsNotUsedAndCanBeDeleted": "A tag não está em uso e pode ser excluída", "TagIsNotUsedAndCanBeDeleted": "A tag não está em uso e pode ser excluída",
"Tags": "Tags", "Tags": "Tags",
"Tasks": "Tarefas", "Tasks": "Tarefas",
"TestAll": "Testar tudo", "TestAll": "Testar Tudo",
"TestAllClients": "Testar todos os clientes", "TestAllClients": "Testar todos os clientes",
"TestAllIndexers": "Testar todos os indexadores", "TestAllIndexers": "Testar todos os indexadores",
"TestAllLists": "Testar todas as listas", "TestAllLists": "Testar todas as listas",
@@ -459,9 +459,8 @@
"StatusEndedContinuing": "Continuação", "StatusEndedContinuing": "Continuação",
"SslCertPasswordHelpTextWarning": "Requer reinício para ter efeito", "SslCertPasswordHelpTextWarning": "Requer reinício para ter efeito",
"SpecificBook": "Livro específico", "SpecificBook": "Livro específico",
"SkipredownloadHelpText": "Impede que o Readarr tente baixar lançamentos alternativos para itens removidos",
"SkipSecondarySeriesBooks": "Ignorar livros de série secundária", "SkipSecondarySeriesBooks": "Ignorar livros de série secundária",
"SkipRedownload": "Ignorar novo download", "SkipRedownload": "Ignorar o Redownload",
"SkipPartBooksAndSets": "Ignorar livros e conjuntos incompletos", "SkipPartBooksAndSets": "Ignorar livros e conjuntos incompletos",
"SkipBooksWithNoISBNOrASIN": "Ignorar livros sem ISBN ou ASIN", "SkipBooksWithNoISBNOrASIN": "Ignorar livros sem ISBN ou ASIN",
"SkipBooksWithMissingReleaseDate": "Ignorar livros com a data de lançamento ausente", "SkipBooksWithMissingReleaseDate": "Ignorar livros com a data de lançamento ausente",
@@ -690,11 +689,11 @@
"TooManyBooks": "Livros ausentes ou muitos? Modifique ou crie um novo", "TooManyBooks": "Livros ausentes ou muitos? Modifique ou crie um novo",
"BlocklistRelease": "Lançamento na lista de bloqueio", "BlocklistRelease": "Lançamento na lista de bloqueio",
"NoHistoryBlocklist": "Nenhum histórico na lista de bloqueio", "NoHistoryBlocklist": "Nenhum histórico na lista de bloqueio",
"Blocklist": "Lista de bloqueio", "Blocklist": "Lista de Bloqueio",
"RemoveFromBlocklist": "Remover da lista de bloqueio", "RemoveFromBlocklist": "Remover da lista de bloqueio",
"UnableToLoadBlocklist": "Incapaz de carregar a lista de bloqueio", "UnableToLoadBlocklist": "Incapaz de carregar a lista de bloqueio",
"ReleaseBranchCheckOfficialBranchMessage": "Ramo {0} não é um ramo válido de lançamentos do Readarr, você não irá receber atualizações", "ReleaseBranchCheckOfficialBranchMessage": "Ramo {0} não é um ramo válido de lançamentos do Readarr, você não irá receber atualizações",
"Time": "Tempo", "Time": "Horário",
"IgnoredMetaHelpText": "Livros irão ser ignorados se eles conterem um ou mais termos (não diferenciando maiúscula de minuscula)", "IgnoredMetaHelpText": "Livros irão ser ignorados se eles conterem um ou mais termos (não diferenciando maiúscula de minuscula)",
"Component": "Componente", "Component": "Componente",
"Level": "Nível", "Level": "Nível",
@@ -743,7 +742,7 @@
"OnRename": "Ao Renomear", "OnRename": "Ao Renomear",
"OnUpgrade": "Ao Atualizar", "OnUpgrade": "Ao Atualizar",
"AppDataLocationHealthCheckMessage": "A atualização não será possível para evitar a exclusão de AppData na atualização", "AppDataLocationHealthCheckMessage": "A atualização não será possível para evitar a exclusão de AppData na atualização",
"IndexerSearchCheckNoInteractiveMessage": "Nenhum indexador disponível com a Pesquisa interativa habilitada, o Readarr não fornecerá nenhum resultado de pesquisa interativo", "IndexerSearchCheckNoInteractiveMessage": "Nenhum indexador disponível com a Pesquisa interativa habilitada, o Readarr não fornecerá nenhum resultado de pesquisa interativa",
"ConnectSettingsSummary": "Notificações, conexões com servidores/tocadores de mídia e scripts personalizados", "ConnectSettingsSummary": "Notificações, conexões com servidores/tocadores de mídia e scripts personalizados",
"DownloadClientStatusCheckAllClientMessage": "Todos os clientes de download estão indisponíveis devido a falhas", "DownloadClientStatusCheckAllClientMessage": "Todos os clientes de download estão indisponíveis devido a falhas",
"DownloadClientsSettingsSummary": "Clientes de download, gerenciamento do download e mapeamento remoto de caminhos", "DownloadClientsSettingsSummary": "Clientes de download, gerenciamento do download e mapeamento remoto de caminhos",
@@ -796,7 +795,7 @@
"ProxyCheckBadRequestMessage": "Falha ao testar o proxy. Código de status: {0}", "ProxyCheckBadRequestMessage": "Falha ao testar o proxy. Código de status: {0}",
"ProxyCheckFailedToTestMessage": "Falha ao testar o proxy: {0}", "ProxyCheckFailedToTestMessage": "Falha ao testar o proxy: {0}",
"QualitySettingsSummary": "Tamanhos de qualidade e nomenclatura", "QualitySettingsSummary": "Tamanhos de qualidade e nomenclatura",
"Queued": "Enfileirado", "Queued": "Enfileirados",
"QueueIsEmpty": "Fila está vazia", "QueueIsEmpty": "Fila está vazia",
"RefreshAndScan": "Atualizar & Escanear", "RefreshAndScan": "Atualizar & Escanear",
"RefreshBook": "Atualizar Livro", "RefreshBook": "Atualizar Livro",
@@ -907,7 +906,7 @@
"IndexerDownloadClientHelpText": "Especifique qual cliente de download é usado para capturas deste indexador", "IndexerDownloadClientHelpText": "Especifique qual cliente de download é usado para capturas deste indexador",
"ListRefreshInterval": "Intervalo de atualização da lista", "ListRefreshInterval": "Intervalo de atualização da lista",
"ListWillRefreshEveryInterp": "A lista será atualizada a cada {0}", "ListWillRefreshEveryInterp": "A lista será atualizada a cada {0}",
"ResetDefinitionTitlesHelpText": "Redefinir títulos da configuração, bem como valores", "ResetDefinitionTitlesHelpText": "Redefinir títulos de definição, bem como valores",
"ResetDefinitions": "Redefinir Definições", "ResetDefinitions": "Redefinir Definições",
"ResetTitles": "Redefinir Títulos", "ResetTitles": "Redefinir Títulos",
"UnableToLoadCustomFormats": "Não foi possível carregar formatos personalizados", "UnableToLoadCustomFormats": "Não foi possível carregar formatos personalizados",
@@ -934,8 +933,8 @@
"RemoveSelectedItemQueueMessageText": "Tem certeza de que deseja remover 1 item da fila?", "RemoveSelectedItemQueueMessageText": "Tem certeza de que deseja remover 1 item da fila?",
"RemoveSelectedItemsQueueMessageText": "Tem certeza de que deseja remover {0} itens da fila?", "RemoveSelectedItemsQueueMessageText": "Tem certeza de que deseja remover {0} itens da fila?",
"Required": "Requerido", "Required": "Requerido",
"ResetQualityDefinitions": "Redefinir Configurações de Qualidade", "ResetQualityDefinitions": "Redefinir Definições de Qualidade",
"ResetQualityDefinitionsMessageText": "Tem certeza de que deseja redefinir as configurações de qualidade?", "ResetQualityDefinitionsMessageText": "Tem certeza de que deseja redefinir as definições de qualidade?",
"BlocklistReleaseHelpText": "Evita que o Readarr pegue automaticamente esses arquivos novamente", "BlocklistReleaseHelpText": "Evita que o Readarr pegue automaticamente esses arquivos novamente",
"NoCutoffUnmetItems": "Nenhum item de corte não atendido", "NoCutoffUnmetItems": "Nenhum item de corte não atendido",
"NoEventsFound": "Nenhum evento encontrado", "NoEventsFound": "Nenhum evento encontrado",
@@ -983,5 +982,6 @@
"ExistingTag": "Etiqueta existente", "ExistingTag": "Etiqueta existente",
"No": "Não", "No": "Não",
"RemoveCompletedDownloads": "Remover downloads concluídos", "RemoveCompletedDownloads": "Remover downloads concluídos",
"RemovingTag": "Removendo etiqueta" "RemovingTag": "Removendo etiqueta",
"SkipRedownloadHelpText": "Impede Readarr de tentar baixar versões alternativas para os itens removidos"
} }

View File

@@ -46,7 +46,7 @@
"Missing": "Lipsește", "Missing": "Lipsește",
"Mode": "Mod", "Mode": "Mod",
"Monitored": "Monitorizat", "Monitored": "Monitorizat",
"MoreInfo": "Mai multă informație", "MoreInfo": "Mai multe informații",
"MustNotContain": "Nu trebuie să conțină", "MustNotContain": "Nu trebuie să conțină",
"Name": "Nume", "Name": "Nume",
"NamingSettings": "Setări de denumire", "NamingSettings": "Setări de denumire",
@@ -74,7 +74,7 @@
"Permissions": "Permisiuni", "Permissions": "Permisiuni",
"Port": "Port", "Port": "Port",
"PortHelpTextWarning": "Necesită repornire pentru a intra în vigoare", "PortHelpTextWarning": "Necesită repornire pentru a intra în vigoare",
"PortNumber": "Numarul portului", "PortNumber": "Număr port",
"PosterSize": "Dimensiunea posterului", "PosterSize": "Dimensiunea posterului",
"PreviewRename": "Previzualizare Redenumire", "PreviewRename": "Previzualizare Redenumire",
"Profiles": "Profile", "Profiles": "Profile",
@@ -101,11 +101,11 @@
"AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Filmele șterse de pe disc sunt automat monitorizate în Radarr", "AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Filmele șterse de pe disc sunt automat monitorizate în Radarr",
"Automatic": "Automat", "Automatic": "Automat",
"BackupFolderHelpText": "Căile relative vor fi în directorul AppData al lui Radarr", "BackupFolderHelpText": "Căile relative vor fi în directorul AppData al lui Radarr",
"BackupNow": "Fă o copie de siguranță", "BackupNow": "Fă o copie de rezervă",
"BackupRetentionHelpText": "Copiile de siguranță automate mai vechi decât perioada de păstrare vor fi curățate automat", "BackupRetentionHelpText": "Copiile de rezervă automate mai vechi decât perioada de păstrare vor fi curățate automat",
"Backups": "Copii de siguranță", "Backups": "Copii de rezervă",
"BindAddress": "Adresa de legare", "BindAddress": "Adresa de legare",
"BindAddressHelpText": "Adresă IP4 validă sau „*” pentru toate interfețele", "BindAddressHelpText": "Adresă IP validă, localhost sau '*' pentru toate interfețele",
"BindAddressHelpTextWarning": "Necesită repornire pentru a intra în vigoare", "BindAddressHelpTextWarning": "Necesită repornire pentru a intra în vigoare",
"BookIsDownloadingInterp": "Filmul se descarcă - {0}% {1}", "BookIsDownloadingInterp": "Filmul se descarcă - {0}% {1}",
"Branch": "Ramură", "Branch": "Ramură",
@@ -124,7 +124,7 @@
"ChownGroupHelpTextWarning": "Acest lucru funcționează numai dacă utilizatorul care rulează Radarr este proprietarul fișierului. Este mai bine să vă asigurați că clientul de descărcare folosește același grup ca Radarr.", "ChownGroupHelpTextWarning": "Acest lucru funcționează numai dacă utilizatorul care rulează Radarr este proprietarul fișierului. Este mai bine să vă asigurați că clientul de descărcare folosește același grup ca Radarr.",
"Clear": "Șterge", "Clear": "Șterge",
"ClickToChangeQuality": "Faceți clic pentru a schimba calitatea", "ClickToChangeQuality": "Faceți clic pentru a schimba calitatea",
"ClientPriority": "Prioritatea clientului", "ClientPriority": "Prioritate client",
"CloneIndexer": "Clonă Indexer", "CloneIndexer": "Clonă Indexer",
"CloneProfile": "Clonați profil", "CloneProfile": "Clonați profil",
"CopyUsingHardlinksHelpTextWarning": "Ocazional, blocarea fișierelor poate împiedica redenumirea fișierelor care sunt însămânțate. Puteți dezactiva temporar însămânțarea și puteți utiliza funcția de redenumire a lui Radarr ca soluție.", "CopyUsingHardlinksHelpTextWarning": "Ocazional, blocarea fișierelor poate împiedica redenumirea fișierelor care sunt însămânțate. Puteți dezactiva temporar însămânțarea și puteți utiliza funcția de redenumire a lui Radarr ca soluție.",
@@ -232,7 +232,7 @@
"IncludeUnknownAuthorItemsHelpText": "Afișați elemente fără film în coadă. Aceasta ar putea include filme eliminate sau orice altceva din categoria lui Radarr", "IncludeUnknownAuthorItemsHelpText": "Afișați elemente fără film în coadă. Aceasta ar putea include filme eliminate sau orice altceva din categoria lui Radarr",
"IncludeUnmonitored": "Includeți Unmonitored", "IncludeUnmonitored": "Includeți Unmonitored",
"Indexer": "Indexator", "Indexer": "Indexator",
"IndexerPriority": "Prioritatea indexatorului", "IndexerPriority": "Prioritate indexator",
"IndexerSettings": "Setări Indexer", "IndexerSettings": "Setări Indexer",
"Indexers": "Indexatori", "Indexers": "Indexatori",
"Interval": "Interval", "Interval": "Interval",
@@ -250,7 +250,7 @@
"LongDateFormat": "Format de dată lungă", "LongDateFormat": "Format de dată lungă",
"ProtocolHelpText": "Alegeți protocolul (protocolele) de utilizat și care este cel preferat atunci când alegeți între versiuni altfel egale", "ProtocolHelpText": "Alegeți protocolul (protocolele) de utilizat și care este cel preferat atunci când alegeți între versiuni altfel egale",
"Proxy": "Proxy", "Proxy": "Proxy",
"ProxyBypassFilterHelpText": "Folosiți „,” ca separator și *. ca un wildcard pentru subdomenii", "ProxyBypassFilterHelpText": "Folosiți ',' ca separator și '*.' ca un wildcard pentru subdomenii",
"ProxyType": "Tip proxy", "ProxyType": "Tip proxy",
"ProxyUsernameHelpText": "Trebuie să introduceți un nume de utilizator și o parolă numai dacă este necesară. Lasă-le necompletate altfel.", "ProxyUsernameHelpText": "Trebuie să introduceți un nume de utilizator și o parolă numai dacă este necesară. Lasă-le necompletate altfel.",
"PublishedDate": "Data publicării", "PublishedDate": "Data publicării",
@@ -428,11 +428,11 @@
"Uptime": "Timp de funcționare", "Uptime": "Timp de funcționare",
"UrlBaseHelpTextWarning": "Necesită repornire pentru a intra în vigoare", "UrlBaseHelpTextWarning": "Necesită repornire pentru a intra în vigoare",
"UseHardlinksInsteadOfCopy": "Folosiți Hardlink-uri în loc de Copy", "UseHardlinksInsteadOfCopy": "Folosiți Hardlink-uri în loc de Copy",
"UseProxy": "Utilizarea proxy", "UseProxy": "Utilizare proxy",
"Usenet": "Usenet", "Usenet": "Usenet",
"UsenetDelay": "Întârziere Usenet", "UsenetDelay": "Întârziere Usenet",
"UsenetDelayHelpText": "Întârziați în câteva minute pentru a aștepta înainte de a lua o eliberare de la Usenet", "UsenetDelayHelpText": "Întârziați în câteva minute pentru a aștepta înainte de a lua o eliberare de la Usenet",
"Username": "Nume de utilizator", "Username": "Nume utilizator",
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Sucursală de utilizat pentru actualizarea Radarr", "UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Sucursală de utilizat pentru actualizarea Radarr",
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Ramură utilizată de mecanismul extern de actualizare", "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Ramură utilizată de mecanismul extern de actualizare",
"Version": "Versiune", "Version": "Versiune",
@@ -466,7 +466,7 @@
"Wanted": "Dorite", "Wanted": "Dorite",
"Component": "Componentă", "Component": "Componentă",
"All": "Toate", "All": "Toate",
"SelectAll": "SelecteazăTot", "SelectAll": "Selectează tot",
"Blocklist": "Listă Neagră", "Blocklist": "Listă Neagră",
"BlocklistRelease": "Lansare pe lista neagră", "BlocklistRelease": "Lansare pe lista neagră",
"FileWasDeletedByViaUI": "Fișierul a fost șters prin interfața de utilizare", "FileWasDeletedByViaUI": "Fișierul a fost șters prin interfața de utilizare",
@@ -597,15 +597,23 @@
"RemovingTag": "Se elimină eticheta", "RemovingTag": "Se elimină eticheta",
"DeleteSelectedImportListsMessageText": "Sigur doriți să ștergeți indexatorul „{0}”?", "DeleteSelectedImportListsMessageText": "Sigur doriți să ștergeți indexatorul „{0}”?",
"DeleteSelectedIndexersMessageText": "Sigur doriți să ștergeți indexatorul „{0}”?", "DeleteSelectedIndexersMessageText": "Sigur doriți să ștergeți indexatorul „{0}”?",
"Yes": "da", "Yes": "Da",
"RedownloadFailed": "Descarcare esuata", "RedownloadFailed": "Descarcare esuata",
"ApplyTagsHelpTextAdd": "Adăugare: adăugați etichetele la lista de etichete existentă", "ApplyTagsHelpTextAdd": "Adăugare: adăugați etichetele la lista de etichete existentă",
"ApplyTagsHelpTextHowToApplyImportLists": "Cum se aplică etichete filmelor selectate", "ApplyTagsHelpTextHowToApplyImportLists": "Cum se aplică etichete listelor de import selectate",
"ApplyTagsHelpTextHowToApplyDownloadClients": "Cum se aplică etichete filmelor selectate", "ApplyTagsHelpTextHowToApplyDownloadClients": "Cum se aplică etichete filmelor selectate",
"ApplyTagsHelpTextHowToApplyIndexers": "Cum se aplică etichete indexatoarelor selectate", "ApplyTagsHelpTextHowToApplyIndexers": "Cum se aplică etichete indexatoarelor selectate",
"ApplyTagsHelpTextRemove": "Eliminați: eliminați etichetele introduse", "ApplyTagsHelpTextRemove": "Eliminați: eliminați etichetele introduse",
"ApplyTagsHelpTextReplace": "Înlocuire: înlocuiți etichetele cu etichetele introduse (nu introduceți etichete pentru a șterge toate etichetele)", "ApplyTagsHelpTextReplace": "Înlocuire: înlocuiți etichetele cu etichetele introduse (nu introduceți etichete pentru a șterge toate etichetele)",
"DeleteSelectedIndexers": "Ștergeți Indexer", "DeleteSelectedIndexers": "Ștergeți Indexer",
"DeleteSelectedDownloadClientsMessageText": "Sigur doriți să ștergeți indexatorul „{0}”?", "DeleteSelectedDownloadClientsMessageText": "Sigur doriți să ștergeți indexatorul „{0}”?",
"SetTags": "Setează Etichete" "SetTags": "Setează Etichete",
"Database": "Bază de date",
"ApplicationURL": "URL aplicație",
"ApplyChanges": "Aplicați modificări",
"AutomaticAdd": "Adăugare automată",
"CloneCondition": "Clonați condiție",
"InstanceName": "Nume instanță",
"Publisher": "Editor",
"Implementation": "Implementarea"
} }

View File

@@ -640,7 +640,6 @@
"ShowName": "Visa Namn", "ShowName": "Visa Namn",
"ShowUnknownAuthorItems": "Visa Okända Författares Saker", "ShowUnknownAuthorItems": "Visa Okända Författares Saker",
"SkipBooksWithMissingReleaseDate": "Hoppa böcker med saknat utgivningsdatum", "SkipBooksWithMissingReleaseDate": "Hoppa böcker med saknat utgivningsdatum",
"SkipredownloadHelpText": "Förhindrar Readarr från att försöka ladda ned alternativa utgåvor för borttagna saker",
"SpecificBook": "Specifik Bok", "SpecificBook": "Specifik Bok",
"StatusEndedDeceased": "Död", "StatusEndedDeceased": "Död",
"TheBooksFilesWillBeDeleted": "Böckernas filer kommer raderas.", "TheBooksFilesWillBeDeleted": "Böckernas filer kommer raderas.",

View File

@@ -487,7 +487,7 @@
"AllExpandedExpandAll": "展开所有", "AllExpandedExpandAll": "展开所有",
"Duration": "时长", "Duration": "时长",
"Filters": "过滤器", "Filters": "过滤器",
"AppDataLocationHealthCheckMessage": "更新将无法阻止在更新时删除 AppData", "AppDataLocationHealthCheckMessage": "正在更新期间的 AppData 不会被更新删除",
"FileWasDeletedByViaUI": "文件已通过 UI 删除", "FileWasDeletedByViaUI": "文件已通过 UI 删除",
"IndexerJackettAll": "使用 Jackett 不受支持的“全部”终点的索引器:{0}", "IndexerJackettAll": "使用 Jackett 不受支持的“全部”终点的索引器:{0}",
"SizeLimit": "尺寸限制", "SizeLimit": "尺寸限制",
@@ -805,7 +805,6 @@
"ShouldMonitorExisting": "监控现有书籍", "ShouldMonitorExisting": "监控现有书籍",
"ShouldSearchHelpText": "在索引器中搜索新添加的项目, 小心使用长列表。", "ShouldSearchHelpText": "在索引器中搜索新添加的项目, 小心使用长列表。",
"ShowTitleHelpText": "在海报下显示作者姓名", "ShowTitleHelpText": "在海报下显示作者姓名",
"SkipredownloadHelpText": "阻止Readarr尝试为删除的项目下载替代版本",
"SpecificBook": "特定书籍", "SpecificBook": "特定书籍",
"TheBooksFilesWillBeDeleted": "此书籍文件将删除。", "TheBooksFilesWillBeDeleted": "此书籍文件将删除。",
"TooManyBooks": "丢失还是过多书籍?修改或创建新的", "TooManyBooks": "丢失还是过多书籍?修改或创建新的",
@@ -916,5 +915,11 @@
"RemoveSelectedItemQueueMessageText": "您确定要从队列中删除 1 项吗?", "RemoveSelectedItemQueueMessageText": "您确定要从队列中删除 1 项吗?",
"RemoveSelectedItemBlocklistMessageText": "您确定要从阻止列表中删除所选项目吗?", "RemoveSelectedItemBlocklistMessageText": "您确定要从阻止列表中删除所选项目吗?",
"RemoveSelectedItemsQueueMessageText": "您确定要从队列中删除 {0} 个项目吗?", "RemoveSelectedItemsQueueMessageText": "您确定要从队列中删除 {0} 个项目吗?",
"ResetQualityDefinitionsMessageText": "您确定要重置质量定义吗?" "ResetQualityDefinitionsMessageText": "您确定要重置质量定义吗?",
"ApplyTagsHelpTextHowToApplyDownloadClients": "如何将标签应用到已选择的下载客户端",
"ApplyTagsHelpTextHowToApplyImportLists": "如何将标签应用到已选择的导入列表",
"ApplyTagsHelpTextHowToApplyIndexers": "如何将标签应用到已选择的索引器",
"ApplyTagsHelpTextRemove": "移除: 移除已输入的标签",
"ApplyTagsHelpTextReplace": "替换: 用输入的标签替换当前标签 (不输入将会清除所有标签)",
"ApplyTagsHelpTextAdd": "添加: 添加标签至已有的标签列表中"
} }

View File

@@ -4,6 +4,7 @@ using System.Linq;
using NLog; using NLog;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.MediaFiles.BookImport; using NzbDrone.Core.MediaFiles.BookImport;
@@ -18,18 +19,21 @@ namespace NzbDrone.Core.MediaFiles
private readonly ITrackedDownloadService _trackedDownloadService; private readonly ITrackedDownloadService _trackedDownloadService;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly ICompletedDownloadService _completedDownloadService; private readonly ICompletedDownloadService _completedDownloadService;
private readonly ICommandResultReporter _commandResultReporter;
private readonly Logger _logger; private readonly Logger _logger;
public DownloadedBooksCommandService(IDownloadedBooksImportService downloadedTracksImportService, public DownloadedBooksCommandService(IDownloadedBooksImportService downloadedTracksImportService,
ITrackedDownloadService trackedDownloadService, ITrackedDownloadService trackedDownloadService,
IDiskProvider diskProvider, IDiskProvider diskProvider,
ICompletedDownloadService completedDownloadService, ICompletedDownloadService completedDownloadService,
ICommandResultReporter commandResultReporter,
Logger logger) Logger logger)
{ {
_downloadedTracksImportService = downloadedTracksImportService; _downloadedTracksImportService = downloadedTracksImportService;
_trackedDownloadService = trackedDownloadService; _trackedDownloadService = trackedDownloadService;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_completedDownloadService = completedDownloadService; _completedDownloadService = completedDownloadService;
_commandResultReporter = commandResultReporter;
_logger = logger; _logger = logger;
} }
@@ -77,9 +81,9 @@ namespace NzbDrone.Core.MediaFiles
if (importResults == null || importResults.All(v => v.Result != ImportResultType.Imported)) if (importResults == null || importResults.All(v => v.Result != ImportResultType.Imported))
{ {
// Atm we don't report it as a command failure, coz that would cause the download to be failed. // Allow the command to complete successfully, but report as unsuccessful
// Changing the message won't do a thing either, coz it will get set to 'Completed' a msec later. _logger.ProgressDebug("Failed to import");
//message.SetMessage("Failed to import"); _commandResultReporter.Report(CommandResult.Unsuccessful);
} }
} }
} }

View File

@@ -1,4 +1,4 @@
using System; using System;
using NzbDrone.Common.Messaging; using NzbDrone.Common.Messaging;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
@@ -10,6 +10,7 @@ namespace NzbDrone.Core.Messaging.Commands
public Command Body { get; set; } public Command Body { get; set; }
public CommandPriority Priority { get; set; } public CommandPriority Priority { get; set; }
public CommandStatus Status { get; set; } public CommandStatus Status { get; set; }
public CommandResult Result { get; set; }
public DateTime QueuedAt { get; set; } public DateTime QueuedAt { get; set; }
public DateTime? StartedAt { get; set; } public DateTime? StartedAt { get; set; }
public DateTime? EndedAt { get; set; } public DateTime? EndedAt { get; set; }

View File

@@ -26,6 +26,7 @@ namespace NzbDrone.Core.Messaging.Commands
CommandModel Get(int id); CommandModel Get(int id);
List<CommandModel> GetStarted(); List<CommandModel> GetStarted();
void SetMessage(CommandModel command, string message); void SetMessage(CommandModel command, string message);
void SetResult(CommandModel command, CommandResult result);
void Start(CommandModel command); void Start(CommandModel command);
void Complete(CommandModel command, string message); void Complete(CommandModel command, string message);
void Fail(CommandModel command, string message, Exception e); void Fail(CommandModel command, string message, Exception e);
@@ -180,6 +181,11 @@ namespace NzbDrone.Core.Messaging.Commands
command.Message = message; command.Message = message;
} }
public void SetResult(CommandModel command, CommandResult result)
{
command.Result = result;
}
public void Start(CommandModel command) public void Start(CommandModel command)
{ {
// Marks the command as started in the DB, the queue takes care of marking it as started on it's own // Marks the command as started in the DB, the queue takes care of marking it as started on it's own
@@ -189,6 +195,12 @@ namespace NzbDrone.Core.Messaging.Commands
public void Complete(CommandModel command, string message) public void Complete(CommandModel command, string message)
{ {
// If the result hasn't been set yet then set it to successful
if (command.Result == CommandResult.Unknown)
{
command.Result = CommandResult.Successful;
}
Update(command, CommandStatus.Completed, message); Update(command, CommandStatus.Completed, message);
_commandQueue.PulseAllConsumers(); _commandQueue.PulseAllConsumers();

View File

@@ -0,0 +1,9 @@
namespace NzbDrone.Core.Messaging.Commands
{
public enum CommandResult
{
Unknown = 0,
Successful = 1,
Unsuccessful = 2
}
}

View File

@@ -0,0 +1,43 @@
using NzbDrone.Core.ProgressMessaging;
namespace NzbDrone.Core.Messaging.Commands
{
public interface ICommandResultReporter
{
void Report(CommandResult result);
}
public class CommandResultReporter : ICommandResultReporter
{
private readonly IManageCommandQueue _commandQueueManager;
public CommandResultReporter(IManageCommandQueue commandQueueManager)
{
_commandQueueManager = commandQueueManager;
}
public void Report(CommandResult result)
{
var command = ProgressMessageContext.CommandModel;
if (command == null)
{
return;
}
if (!ProgressMessageContext.LockReentrancy())
{
return;
}
try
{
_commandQueueManager.SetResult(command, result);
}
finally
{
ProgressMessageContext.UnlockReentrancy();
}
}
}
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Net; using System.Net;
using NzbDrone.Core.Exceptions; using NzbDrone.Core.Exceptions;
@@ -14,5 +15,10 @@ namespace NzbDrone.Core.MetadataSource.BookInfo
: base(HttpStatusCode.ServiceUnavailable, message, args) : base(HttpStatusCode.ServiceUnavailable, message, args)
{ {
} }
public BookInfoException(string message, Exception innerException, params object[] args)
: base(HttpStatusCode.ServiceUnavailable, message, innerException, args)
{
}
} }
} }

View File

@@ -1,4 +1,5 @@
using System.Net; using System;
using System.Net;
using NzbDrone.Core.Exceptions; using NzbDrone.Core.Exceptions;
namespace NzbDrone.Core.MetadataSource.Goodreads namespace NzbDrone.Core.MetadataSource.Goodreads
@@ -14,5 +15,10 @@ namespace NzbDrone.Core.MetadataSource.Goodreads
: base(HttpStatusCode.ServiceUnavailable, message, args) : base(HttpStatusCode.ServiceUnavailable, message, args)
{ {
} }
public GoodreadsException(string message, Exception innerException, params object[] args)
: base(HttpStatusCode.ServiceUnavailable, message, innerException, args)
{
}
} }
} }

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net;
using NLog; using NLog;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Http; using NzbDrone.Core.Http;
@@ -43,14 +44,20 @@ namespace NzbDrone.Core.MetadataSource.Goodreads
return response.Resource; return response.Resource;
} }
catch (HttpException) catch (HttpException ex)
{ {
throw new GoodreadsException("Search for '{0}' failed. Unable to communicate with Goodreads.", query); _logger.Warn(ex);
throw new GoodreadsException("Search for '{0}' failed. Unable to communicate with Goodreads.", ex, query);
}
catch (WebException ex)
{
_logger.Warn(ex);
throw new GoodreadsException("Search for '{0}' failed. Unable to communicate with Goodreads.", ex, query, ex.Message);
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Warn(ex, ex.Message); _logger.Warn(ex);
throw new GoodreadsException("Search for '{0}' failed. Invalid response received from Goodreads.", query); throw new GoodreadsException("Search for '{0}' failed. Invalid response received from Goodreads.", ex, query);
} }
} }
} }

View File

@@ -7,7 +7,7 @@ namespace NzbDrone.Integration.Test.ApiTests
public class AuthorLookupFixture : IntegrationTest public class AuthorLookupFixture : IntegrationTest
{ {
[TestCase("Robert Harris", "Robert Harris")] [TestCase("Robert Harris", "Robert Harris")]
[TestCase("J.K. Rowling", "J.K. Rowling")] [TestCase("Philip W. Errington", "Philip W. Errington")]
public void lookup_new_author_by_name(string term, string name) public void lookup_new_author_by_name(string term, string name)
{ {
var author = Author.Lookup(term); var author = Author.Lookup(term);

View File

@@ -17,6 +17,7 @@ namespace Readarr.Api.V1.Commands
public Command Body { get; set; } public Command Body { get; set; }
public CommandPriority Priority { get; set; } public CommandPriority Priority { get; set; }
public CommandStatus Status { get; set; } public CommandStatus Status { get; set; }
public CommandResult Result { get; set; }
public DateTime Queued { get; set; } public DateTime Queued { get; set; }
public DateTime? Started { get; set; } public DateTime? Started { get; set; }
public DateTime? Ended { get; set; } public DateTime? Ended { get; set; }
@@ -102,6 +103,7 @@ namespace Readarr.Api.V1.Commands
Body = model.Body, Body = model.Body,
Priority = model.Priority, Priority = model.Priority,
Status = model.Status, Status = model.Status,
Result = model.Result,
Queued = model.QueuedAt, Queued = model.QueuedAt,
Started = model.StartedAt, Started = model.StartedAt,
Ended = model.EndedAt, Ended = model.EndedAt,

View File

@@ -1,9 +1,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FluentValidation; using FluentValidation;
using FluentValidation.Results;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.CustomFormats; using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Validation;
using NzbDrone.Http.REST.Attributes; using NzbDrone.Http.REST.Attributes;
using Readarr.Http; using Readarr.Http;
using Readarr.Http.REST; using Readarr.Http.REST;
@@ -50,6 +52,9 @@ namespace Readarr.Api.V1.CustomFormats
public ActionResult<CustomFormatResource> Create(CustomFormatResource customFormatResource) public ActionResult<CustomFormatResource> Create(CustomFormatResource customFormatResource)
{ {
var model = customFormatResource.ToModel(_specifications); var model = customFormatResource.ToModel(_specifications);
Validate(model);
return Created(_formatService.Insert(model).Id); return Created(_formatService.Insert(model).Id);
} }
@@ -58,6 +63,9 @@ namespace Readarr.Api.V1.CustomFormats
public ActionResult<CustomFormatResource> Update(CustomFormatResource resource) public ActionResult<CustomFormatResource> Update(CustomFormatResource resource)
{ {
var model = resource.ToModel(_specifications); var model = resource.ToModel(_specifications);
Validate(model);
_formatService.Update(model); _formatService.Update(model);
return Accepted(model.Id); return Accepted(model.Id);
@@ -91,6 +99,24 @@ namespace Readarr.Api.V1.CustomFormats
return schema; return schema;
} }
private void Validate(CustomFormat definition)
{
foreach (var validationResult in definition.Specifications.Select(spec => spec.Validate()))
{
VerifyValidationResult(validationResult);
}
}
private void VerifyValidationResult(ValidationResult validationResult)
{
var result = new NzbDroneValidationResult(validationResult.Errors);
if (!result.IsValid)
{
throw new ValidationException(result.Errors);
}
}
private IEnumerable<ICustomFormatSpecification> GetPresets() private IEnumerable<ICustomFormatSpecification> GetPresets()
{ {
yield return new ReleaseTitleSpecification yield return new ReleaseTitleSpecification

View File

@@ -69,9 +69,9 @@ namespace Readarr.Api.V1.Queue
} }
[RestDeleteById] [RestDeleteById]
public void RemoveAction(int id, bool removeFromClient = true, bool blocklist = false, bool skipReDownload = false) public void RemoveAction(int id, bool removeFromClient = true, bool blocklist = false, bool skipRedownload = false)
{ {
var trackedDownload = Remove(id, removeFromClient, blocklist, skipReDownload); var trackedDownload = Remove(id, removeFromClient, blocklist, skipRedownload);
if (trackedDownload != null) if (trackedDownload != null)
{ {
@@ -80,13 +80,13 @@ namespace Readarr.Api.V1.Queue
} }
[HttpDelete("bulk")] [HttpDelete("bulk")]
public object RemoveMany([FromBody] QueueBulkResource resource, [FromQuery] bool removeFromClient = true, [FromQuery] bool blocklist = false, [FromQuery] bool skipReDownload = false) public object RemoveMany([FromBody] QueueBulkResource resource, [FromQuery] bool removeFromClient = true, [FromQuery] bool blocklist = false, [FromQuery] bool skipRedownload = false)
{ {
var trackedDownloadIds = new List<string>(); var trackedDownloadIds = new List<string>();
foreach (var id in resource.Ids) foreach (var id in resource.Ids)
{ {
var trackedDownload = Remove(id, removeFromClient, blocklist, skipReDownload); var trackedDownload = Remove(id, removeFromClient, blocklist, skipRedownload);
if (trackedDownload != null) if (trackedDownload != null)
{ {
@@ -205,7 +205,7 @@ namespace Readarr.Api.V1.Queue
} }
} }
private TrackedDownload Remove(int id, bool removeFromClient, bool blocklist, bool skipReDownload) private TrackedDownload Remove(int id, bool removeFromClient, bool blocklist, bool skipRedownload)
{ {
var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id); var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id);
@@ -238,7 +238,7 @@ namespace Readarr.Api.V1.Queue
if (blocklist) if (blocklist)
{ {
_failedDownloadService.MarkAsFailed(trackedDownload.DownloadItem.DownloadId, skipReDownload); _failedDownloadService.MarkAsFailed(trackedDownload.DownloadItem.DownloadId, skipRedownload);
} }
if (!removeFromClient && !blocklist) if (!removeFromClient && !blocklist)

View File

@@ -6190,7 +6190,7 @@
} }
}, },
{ {
"name": "skipReDownload", "name": "skipRedownload",
"in": "query", "in": "query",
"schema": { "schema": {
"type": "boolean", "type": "boolean",
@@ -6228,7 +6228,7 @@
} }
}, },
{ {
"name": "skipReDownload", "name": "skipRedownload",
"in": "query", "in": "query",
"schema": { "schema": {
"type": "boolean", "type": "boolean",
@@ -9370,6 +9370,9 @@
"status": { "status": {
"$ref": "#/components/schemas/CommandStatus" "$ref": "#/components/schemas/CommandStatus"
}, },
"result": {
"$ref": "#/components/schemas/CommandResult"
},
"queued": { "queued": {
"type": "string", "type": "string",
"format": "date-time" "format": "date-time"
@@ -9417,6 +9420,14 @@
}, },
"additionalProperties": false "additionalProperties": false
}, },
"CommandResult": {
"enum": [
"unknown",
"successful",
"unsuccessful"
],
"type": "string"
},
"CommandStatus": { "CommandStatus": {
"enum": [ "enum": [
"queued", "queued",

View File

@@ -107,7 +107,7 @@ namespace Readarr.Http.ClientSchema
Placeholder = fieldAttribute.Placeholder Placeholder = fieldAttribute.Placeholder
}; };
if (fieldAttribute.Type == FieldType.Select || fieldAttribute.Type == FieldType.TagSelect) if (fieldAttribute.Type is FieldType.Select or FieldType.TagSelect)
{ {
if (fieldAttribute.SelectOptionsProviderAction.IsNotNullOrWhiteSpace()) if (fieldAttribute.SelectOptionsProviderAction.IsNotNullOrWhiteSpace())
{ {
@@ -154,33 +154,40 @@ namespace Readarr.Http.ClientSchema
private static List<SelectOption> GetSelectOptions(Type selectOptions) private static List<SelectOption> GetSelectOptions(Type selectOptions)
{ {
var options = selectOptions.GetFields().Where(v => v.IsStatic).Select(v => if (selectOptions.IsEnum)
{ {
var name = v.Name.Replace('_', ' '); var options = selectOptions
var value = Convert.ToInt32(v.GetRawConstantValue()); .GetFields()
var attrib = v.GetCustomAttribute<FieldOptionAttribute>(); .Where(v => v.IsStatic && !v.GetCustomAttributes(false).OfType<ObsoleteAttribute>().Any())
if (attrib != null) .Select(v =>
{
return new SelectOption
{ {
Value = value, var name = v.Name.Replace('_', ' ');
Name = attrib.Label ?? name, var value = Convert.ToInt32(v.GetRawConstantValue());
Order = attrib.Order, var attrib = v.GetCustomAttribute<FieldOptionAttribute>();
Hint = attrib.Hint ?? $"({value})"
};
}
else
{
return new SelectOption
{
Value = value,
Name = name,
Order = value
};
}
});
return options.OrderBy(o => o.Order).ToList(); if (attrib != null)
{
return new SelectOption
{
Value = value,
Name = attrib.Label ?? name,
Order = attrib.Order,
Hint = attrib.Hint ?? $"({value})"
};
}
return new SelectOption
{
Value = value,
Name = name,
Order = value
};
});
return options.OrderBy(o => o.Order).ToList();
}
throw new NotSupportedException();
} }
private static Func<object, object> GetValueConverter(Type propertyType) private static Func<object, object> GetValueConverter(Type propertyType)
@@ -241,7 +248,7 @@ namespace Readarr.Http.ClientSchema
} }
else else
{ {
return fieldValue.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); return fieldValue.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => v.Trim());
} }
}; };
} }