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'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '0.2.4'
majorVersion: '0.3.1'
minorVersion: $[counter('minorVersion', 1)]
readarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(readarrVersion)'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import MoveAuthorModal from 'Author/MoveAuthor/MoveAuthorModal';
import MetadataProfileSelectInputConnector from 'Components/Form/MetadataProfileSelectInputConnector';
import MonitorNewItemsSelectInput from 'Components/Form/MonitorNewItemsSelectInput';
@@ -9,6 +10,7 @@ import SelectInput from 'Components/Form/SelectInput';
import SpinnerButton from 'Components/Link/SpinnerButton';
import PageContentFooter from 'Components/Page/PageContentFooter';
import { kinds } from 'Helpers/Props';
import { fetchRootFolders } from 'Store/Actions/Settings/rootFolders';
import translate from 'Utilities/String/translate';
import AuthorEditorFooterLabel from './AuthorEditorFooterLabel';
import DeleteAuthorModal from './Delete/DeleteAuthorModal';
@@ -17,6 +19,10 @@ import styles from './AuthorEditorFooter.css';
const NO_CHANGE = 'noChange';
const mapDispatchToProps = {
dispatchFetchRootFolders: fetchRootFolders
};
class AuthorEditorFooter extends Component {
//
@@ -39,6 +45,13 @@ class AuthorEditorFooter extends Component {
};
}
//
// Lifecycle
componentDidMount() {
this.props.dispatchFetchRootFolders();
}
componentDidUpdate(prevProps) {
const {
isSaving,
@@ -341,7 +354,8 @@ AuthorEditorFooter.propTypes = {
showMetadataProfile: PropTypes.bool.isRequired,
onSaveSelected: 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
} = props;
const progress = bookCount ? bookFileCount / bookCount * 100 : 100;
const text = `${bookFileCount} / ${bookCount}`;
const progress = bookCount ? bookCount / totalBookCount * 100 : 100;
const text = `${bookCount} / ${totalBookCount}`;
return (
<ProgressBar

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,24 +13,51 @@ class InlineMarkdown extends Component {
data
} = this.props;
// For now only replace links
// For now only replace links or code blocks (not both)
const markdownBlocks = [];
if (data) {
const regex = RegExp(/\[(.+?)\]\((.+?)\)/g);
const linkRegex = RegExp(/\[(.+?)\]\((.+?)\)/g);
let endIndex = 0;
let match = null;
while ((match = regex.exec(data)) !== null) {
while ((match = linkRegex.exec(data)) !== null) {
if (match.index > endIndex) {
markdownBlocks.push(data.substr(endIndex, match.index - endIndex));
}
markdownBlocks.push(<Link key={match.index} to={match[2]}>{match[1]}</Link>);
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));
}
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>;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -65,12 +65,21 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
}
[Test]
public void should_use_pagesize_reported_by_caps()
public void should_use_best_pagesize_reported_by_caps()
{
_caps.MaxPageSize = 30;
_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]

View File

@@ -103,12 +103,21 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
}
[Test]
public void should_use_pagesize_reported_by_caps()
public void should_use_best_pagesize_reported_by_caps()
{
_caps.MaxPageSize = 30;
_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")]

View File

@@ -41,8 +41,8 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
}
[TestCase("Robert Harris", "Robert Harris")]
[TestCase("James Patterson", "James Patterson")]
[TestCase("Antoine de Saint-Exupéry", "Antoine de Saint-Exupéry")]
[TestCase("Lyndsay Ely", "Lyndsay Ely")]
[TestCase("Elisa Puricelli Guerra", "Elisa Puricelli Guerra")]
public void successful_author_search(string title, string expected)
{
var result = Subject.SearchForNewAuthor(title);
@@ -54,7 +54,7 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
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("asin:B0192CTMYG", null, "Harry Potter and the Sorcerer's Stone")]
@@ -85,8 +85,8 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
ExceptionVerification.IgnoreWarns();
}
[TestCase("Philip Pullman", 0, typeof(Author), new[] { "Philip Pullman" }, TestName = "author")]
[TestCase("Philip Pullman", 1, typeof(Book), new[] { "Northern Lights", "The Amber Spyglass" }, TestName = "book")]
[TestCase("Catherine Butler", 0, typeof(Author), new[] { "Catherine Butler" }, TestName = "author")]
[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)
{
var result = Subject.SearchForNewEntity(query);
@@ -97,7 +97,7 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
{
var cast = result[position] as Author;
cast.Should().NotBeNull();
cast.Name.Should().ContainAll(expected);
cast.Name.Should().ContainAny(expected);
}
else
{

View File

@@ -25,8 +25,8 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
}
[TestCase("Robert Harris", 575)]
[TestCase("James Patterson", 3780)]
[TestCase("Antoine de Saint-Exupéry", 1020792)]
[TestCase("Lyndsay Ely", 8056539)]
[TestCase("Elisa Puricelli Guerra", 4481805)]
public void successful_author_search(string title, int expected)
{
var result = Subject.Search(title);
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
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("9780439554930", 48517161)]
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 Dictionary<string, string> Data { 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
{
void MarkAsFailed(int historyId, bool skipReDownload = false);
void MarkAsFailed(string downloadId, bool skipReDownload = false);
void MarkAsFailed(int historyId, bool skipRedownload = false);
void MarkAsFailed(string downloadId, bool skipRedownload = false);
void Check(TrackedDownload trackedDownload);
void ProcessFailed(TrackedDownload trackedDownload);
}
@@ -30,14 +30,14 @@ namespace NzbDrone.Core.Download
_eventAggregator = eventAggregator;
}
public void MarkAsFailed(int historyId, bool skipReDownload = false)
public void MarkAsFailed(int historyId, bool skipRedownload = false)
{
var history = _historyService.Get(historyId);
var downloadId = history.DownloadId;
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
{
@@ -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);
@@ -54,7 +54,7 @@ namespace NzbDrone.Core.Download
{
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);
}
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();
@@ -129,7 +129,7 @@ namespace NzbDrone.Core.Download
Message = message,
Data = historyItem.Data,
TrackedDownload = trackedDownload,
SkipReDownload = skipReDownload
SkipRedownload = skipRedownload
};
_eventAggregator.PublishEvent(downloadFailedEvent);

View File

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

View File

@@ -1,4 +1,5 @@
using System.Net;
using System;
using System.Net;
using NzbDrone.Common.Exceptions;
namespace NzbDrone.Core.Exceptions
@@ -13,6 +14,12 @@ namespace NzbDrone.Core.Exceptions
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)
: base(message)
{

View File

@@ -8,6 +8,7 @@ using NzbDrone.Core.ThingiProvider.Events;
namespace NzbDrone.Core.HealthCheck.Checks
{
[CheckOn(typeof(ProviderAddedEvent<IIndexer>))]
[CheckOn(typeof(ProviderUpdatedEvent<IIndexer>))]
[CheckOn(typeof(ProviderDeletedEvent<IIndexer>))]
[CheckOn(typeof(ProviderStatusChangedEvent<IIndexer>))]
@@ -23,12 +24,15 @@ namespace NzbDrone.Core.HealthCheck.Checks
public override HealthCheck Check()
{
var jackettAllProviders = _providerFactory.All().Where(
i => i.ConfigContract.Equals("TorznabSettings") &&
((i.Settings as TorznabSettings).BaseUrl.Contains("/torznab/all/api", StringComparison.InvariantCultureIgnoreCase) ||
(i.Settings as TorznabSettings).BaseUrl.Contains("/api/v2.0/indexers/all/results/torznab", StringComparison.InvariantCultureIgnoreCase) ||
(i.Settings as TorznabSettings).ApiPath.Contains("/torznab/all/api", StringComparison.InvariantCultureIgnoreCase) ||
(i.Settings as TorznabSettings).ApiPath.Contains("/api/v2.0/indexers/all/results/torznab", StringComparison.InvariantCultureIgnoreCase)));
var jackettAllProviders = _providerFactory.All()
.Where(
i => i.Enable &&
i.ConfigContract.Equals("TorznabSettings") &&
(((TorznabSettings)i.Settings).BaseUrl.Contains("/torznab/all/api", 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())
{
@@ -37,8 +41,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
return new HealthCheck(GetType(),
HealthCheckResult.Warning,
string.Format(_localizationService.GetLocalizedString("IndexerJackettAll"),
string.Join(", ", jackettAllProviders.Select(i => i.Name))),
string.Format(_localizationService.GetLocalizedString("IndexerJackettAll"), string.Join(", ", jackettAllProviders.Select(i => i.Name))),
"#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)
{
var downloadDecisions = new List<DownloadDecision>();
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)
{
var downloadDecisions = new List<DownloadDecision>();
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)
@@ -150,5 +162,13 @@ namespace NzbDrone.Core.IndexerSearch
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 =>
{
c.Guid = string.Concat(Definition.Id, "_", c.Guid);
c.IndexerId = Definition.Id;
c.Indexer = Definition.Name;
c.DownloadProtocol = Protocol;

View File

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

View File

@@ -102,10 +102,19 @@ namespace NzbDrone.Core.Indexers
{
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);
}
else
{
_indexerStatusService.RecordFailure(definition.Id);
}
return result;
}

View File

@@ -19,8 +19,13 @@ namespace NzbDrone.Core.Indexers.Newznab
public override string Name => "Newznab";
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()
{
@@ -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)
{
return new IndexerDefinition
@@ -163,5 +162,17 @@ namespace NzbDrone.Core.Indexers.Newznab
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 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()
{
@@ -35,12 +41,6 @@ namespace NzbDrone.Core.Indexers.Torznab
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)
{
return new IndexerDefinition
@@ -147,5 +147,17 @@ namespace NzbDrone.Core.Indexers.Torznab
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.",
"TotalBookCountBooksTotalBookFileCountBooksWithFilesInterp": "{0} Titel insgesamt. {1} Titel mit Dateien.",
"SearchFiltered": "Suche gefilterte",
"SkipredownloadHelpText": "Verhindert, dass Lidarr versucht alternative Veröffentlichungen für die entfernten Objekte herunterzuladen",
"AddList": "Liste hinzufügen",
"InstanceName": "Instanzname",
"InstanceNameHelpText": "Instanzname im Browser-Tab und für Syslog-Anwendungsname",

View File

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

View File

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

View File

@@ -761,7 +761,6 @@
"ContinuingNoAdditionalBooksAreExpected": "Abumeita ei odoteta lisää",
"MusicBrainzBookID": "MusicBrainz-kappaletunniste",
"SearchForAllCutoffUnmetBooks": "Etsi kaikkia albumeita, joiden katkaisutasoa ei ole savutettu",
"SkipredownloadHelpText": "Estää poistettujen kohteiden vaihtoehtoisten julkaisujen latauksen.",
"SearchForMonitoredBooks": "Etsi valvottuja albumeita",
"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.",

View File

@@ -597,7 +597,6 @@
"StatusEndedDeceased": "Elhunyt",
"StatusEndedContinuing": "Folytatás",
"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",
"SkipRedownload": "Az újraletöltés átugrá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",
"ShouldSearchHelpText": "Pesquisar indexadores para novos itens adicionados. Utilize com cuidado para listas grandes.",
"SkipPartBooksAndSets": "Ignorar livros e conjuntos incompletos",
"SkipredownloadHelpText": "Impede que o Readarr tente transferir versões alternativas para itens removidos",
"ASIN": "ASIN",
"Continuing": "Continuação",
"ConsoleLogLevel": "Nível de registo do console",

View File

@@ -17,13 +17,13 @@
"AgeWhenGrabbed": "Tempo de vida (quando obtido)",
"ApiKeyHelpTextWarning": "Requer reinício para ter efeito",
"LoadingBooksFailed": "Falha ao carregar livros",
"Logs": "Registros",
"Logs": "Logs",
"MustContain": "Deve conter",
"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",
"UnableToLoadMetadataProfiles": "Não foi possível carregar os perfis de metadados",
"AddListExclusion": "Adicionar exclusão à lista",
"AddingTag": "Adicionando etiqueta",
"AddingTag": "Adicionar tag",
"AlreadyInYourLibrary": "Já está na sua biblioteca",
"AlternateTitles": "Títulos alternativos",
"Analytics": "Análises",
@@ -37,14 +37,14 @@
"AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Livros excluídos do disco deixam de ser monitorados no Readarr automaticamente",
"Automatic": "Automático",
"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",
"Backups": "Backups",
"BindAddress": "Fixar Endereço",
"BindAddressHelpText": "Endereço IP válido, localhost ou '*' para todas as interfaces",
"BindAddressHelpTextWarning": "Requer reiniciar para ter efeito",
"BookIsDownloading": "O livro está baixando",
"DiskSpace": "Espaço em disco",
"DiskSpace": "Espaço em Disco",
"Docker": "Docker",
"DownloadClient": "Cliente de download",
"DownloadClientSettings": "Configurações do cliente de download",
@@ -107,7 +107,7 @@
"DelayProfiles": "Perfis de atraso",
"DelayingDownloadUntilInterp": "Atrasando o download até {0} às {1}",
"Delete": "Excluir",
"DeleteBackup": "Excluir backup",
"DeleteBackup": "Excluir Backup",
"DeleteBackupMessageText": "Tem certeza que deseja excluir o backup \"{0}\"?",
"DeleteDelayProfile": "Excluir 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.",
"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.",
"GeneralSettings": "Configurações gerais",
"GeneralSettings": "Configurações Gerais",
"Global": "Global",
"GoToInterp": "Ir para {0}",
"Grab": "Obter",
@@ -225,11 +225,11 @@
"New": "Novo",
"NoBackupsAreAvailable": "Não há backups disponíveis",
"NoHistory": "Sem histórico.",
"NoLeaveIt": "Não, deixe assim",
"NoLeaveIt": "Não, deixe-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",
"NoUpdatesAreAvailable": "Não há atualizações disponíveis",
"NoUpdatesAreAvailable": "Nenhuma atualização está disponível",
"None": "Nenhum",
"NotificationTriggers": "Acionadores da notificação",
"OnGrabHelpText": "Ao obter",
@@ -269,7 +269,7 @@
"Queue": "Fila",
"RSSSync": "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.",
"ReadarrTags": "Tags do Readarr",
"Real": "Real",
@@ -363,7 +363,7 @@
"StandardBookFormat": "Formato de livro padrão",
"StartTypingOrSelectAPathBelow": "Comece a digitar ou selecione um caminho abaixo",
"StartupDirectory": "Diretório de inicialização",
"Status": "Status",
"Status": "Estado",
"StatusEndedEnded": "Terminado",
"Style": "Estilo",
"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",
"Tags": "Tags",
"Tasks": "Tarefas",
"TestAll": "Testar tudo",
"TestAll": "Testar Tudo",
"TestAllClients": "Testar todos os clientes",
"TestAllIndexers": "Testar todos os indexadores",
"TestAllLists": "Testar todas as listas",
@@ -459,9 +459,8 @@
"StatusEndedContinuing": "Continuação",
"SslCertPasswordHelpTextWarning": "Requer reinício para ter efeito",
"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",
"SkipRedownload": "Ignorar novo download",
"SkipRedownload": "Ignorar o Redownload",
"SkipPartBooksAndSets": "Ignorar livros e conjuntos incompletos",
"SkipBooksWithNoISBNOrASIN": "Ignorar livros sem ISBN ou ASIN",
"SkipBooksWithMissingReleaseDate": "Ignorar livros com a data de lançamento ausente",
@@ -690,11 +689,11 @@
"TooManyBooks": "Livros ausentes ou muitos? Modifique ou crie um novo",
"BlocklistRelease": "Lançamento 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",
"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",
"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)",
"Component": "Componente",
"Level": "Nível",
@@ -743,7 +742,7 @@
"OnRename": "Ao Renomear",
"OnUpgrade": "Ao Atualizar",
"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",
"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",
@@ -796,7 +795,7 @@
"ProxyCheckBadRequestMessage": "Falha ao testar o proxy. Código de status: {0}",
"ProxyCheckFailedToTestMessage": "Falha ao testar o proxy: {0}",
"QualitySettingsSummary": "Tamanhos de qualidade e nomenclatura",
"Queued": "Enfileirado",
"Queued": "Enfileirados",
"QueueIsEmpty": "Fila está vazia",
"RefreshAndScan": "Atualizar & Escanear",
"RefreshBook": "Atualizar Livro",
@@ -907,7 +906,7 @@
"IndexerDownloadClientHelpText": "Especifique qual cliente de download é usado para capturas deste indexador",
"ListRefreshInterval": "Intervalo de atualização da lista",
"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",
"ResetTitles": "Redefinir Títulos",
"UnableToLoadCustomFormats": "Não foi possível carregar formatos personalizados",
@@ -934,8 +933,8 @@
"RemoveSelectedItemQueueMessageText": "Tem certeza de que deseja remover 1 item da fila?",
"RemoveSelectedItemsQueueMessageText": "Tem certeza de que deseja remover {0} itens da fila?",
"Required": "Requerido",
"ResetQualityDefinitions": "Redefinir Configurações de Qualidade",
"ResetQualityDefinitionsMessageText": "Tem certeza de que deseja redefinir as configurações de qualidade?",
"ResetQualityDefinitions": "Redefinir Definiçõ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",
"NoCutoffUnmetItems": "Nenhum item de corte não atendido",
"NoEventsFound": "Nenhum evento encontrado",
@@ -983,5 +982,6 @@
"ExistingTag": "Etiqueta existente",
"No": "Não",
"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",
"Mode": "Mod",
"Monitored": "Monitorizat",
"MoreInfo": "Mai multă informație",
"MoreInfo": "Mai multe informații",
"MustNotContain": "Nu trebuie să conțină",
"Name": "Nume",
"NamingSettings": "Setări de denumire",
@@ -74,7 +74,7 @@
"Permissions": "Permisiuni",
"Port": "Port",
"PortHelpTextWarning": "Necesită repornire pentru a intra în vigoare",
"PortNumber": "Numarul portului",
"PortNumber": "Număr port",
"PosterSize": "Dimensiunea posterului",
"PreviewRename": "Previzualizare Redenumire",
"Profiles": "Profile",
@@ -101,11 +101,11 @@
"AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Filmele șterse de pe disc sunt automat monitorizate în Radarr",
"Automatic": "Automat",
"BackupFolderHelpText": "Căile relative vor fi în directorul AppData al lui Radarr",
"BackupNow": "Fă o copie de siguranță",
"BackupRetentionHelpText": "Copiile de siguranță automate mai vechi decât perioada de păstrare vor fi curățate automat",
"Backups": "Copii de siguranță",
"BackupNow": "Fă o copie de rezervă",
"BackupRetentionHelpText": "Copiile de rezervă automate mai vechi decât perioada de păstrare vor fi curățate automat",
"Backups": "Copii de rezervă",
"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",
"BookIsDownloadingInterp": "Filmul se descarcă - {0}% {1}",
"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.",
"Clear": "Șterge",
"ClickToChangeQuality": "Faceți clic pentru a schimba calitatea",
"ClientPriority": "Prioritatea clientului",
"ClientPriority": "Prioritate client",
"CloneIndexer": "Clonă Indexer",
"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.",
@@ -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",
"IncludeUnmonitored": "Includeți Unmonitored",
"Indexer": "Indexator",
"IndexerPriority": "Prioritatea indexatorului",
"IndexerPriority": "Prioritate indexator",
"IndexerSettings": "Setări Indexer",
"Indexers": "Indexatori",
"Interval": "Interval",
@@ -250,7 +250,7 @@
"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",
"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",
"ProxyUsernameHelpText": "Trebuie să introduceți un nume de utilizator și o parolă numai dacă este necesară. Lasă-le necompletate altfel.",
"PublishedDate": "Data publicării",
@@ -428,11 +428,11 @@
"Uptime": "Timp de funcționare",
"UrlBaseHelpTextWarning": "Necesită repornire pentru a intra în vigoare",
"UseHardlinksInsteadOfCopy": "Folosiți Hardlink-uri în loc de Copy",
"UseProxy": "Utilizarea proxy",
"UseProxy": "Utilizare proxy",
"Usenet": "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",
"Username": "Nume de utilizator",
"Username": "Nume utilizator",
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Sucursală de utilizat pentru actualizarea Radarr",
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Ramură utilizată de mecanismul extern de actualizare",
"Version": "Versiune",
@@ -466,7 +466,7 @@
"Wanted": "Dorite",
"Component": "Componentă",
"All": "Toate",
"SelectAll": "SelecteazăTot",
"SelectAll": "Selectează tot",
"Blocklist": "Listă Neagră",
"BlocklistRelease": "Lansare pe lista neagră",
"FileWasDeletedByViaUI": "Fișierul a fost șters prin interfața de utilizare",
@@ -597,15 +597,23 @@
"RemovingTag": "Se elimină eticheta",
"DeleteSelectedImportListsMessageText": "Sigur doriți să ștergeți indexatorul „{0}”?",
"DeleteSelectedIndexersMessageText": "Sigur doriți să ștergeți indexatorul „{0}”?",
"Yes": "da",
"Yes": "Da",
"RedownloadFailed": "Descarcare esuata",
"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",
"ApplyTagsHelpTextHowToApplyIndexers": "Cum se aplică etichete indexatoarelor selectate",
"ApplyTagsHelpTextRemove": "Eliminați: eliminați etichetele introduse",
"ApplyTagsHelpTextReplace": "Înlocuire: înlocuiți etichetele cu etichetele introduse (nu introduceți etichete pentru a șterge toate etichetele)",
"DeleteSelectedIndexers": "Ștergeți Indexer",
"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",
"ShowUnknownAuthorItems": "Visa Okända Författares Saker",
"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",
"StatusEndedDeceased": "Död",
"TheBooksFilesWillBeDeleted": "Böckernas filer kommer raderas.",

View File

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

View File

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

View File

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

View File

@@ -26,6 +26,7 @@ namespace NzbDrone.Core.Messaging.Commands
CommandModel Get(int id);
List<CommandModel> GetStarted();
void SetMessage(CommandModel command, string message);
void SetResult(CommandModel command, CommandResult result);
void Start(CommandModel command);
void Complete(CommandModel command, string message);
void Fail(CommandModel command, string message, Exception e);
@@ -180,6 +181,11 @@ namespace NzbDrone.Core.Messaging.Commands
command.Message = message;
}
public void SetResult(CommandModel command, CommandResult result)
{
command.Result = result;
}
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
@@ -189,6 +195,12 @@ namespace NzbDrone.Core.Messaging.Commands
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);
_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 NzbDrone.Core.Exceptions;
@@ -14,5 +15,10 @@ namespace NzbDrone.Core.MetadataSource.BookInfo
: 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;
namespace NzbDrone.Core.MetadataSource.Goodreads
@@ -14,5 +15,10 @@ namespace NzbDrone.Core.MetadataSource.Goodreads
: 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.Collections.Generic;
using System.Net;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Http;
@@ -43,14 +44,20 @@ namespace NzbDrone.Core.MetadataSource.Goodreads
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)
{
_logger.Warn(ex, ex.Message);
throw new GoodreadsException("Search for '{0}' failed. Invalid response received from Goodreads.", query);
_logger.Warn(ex);
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
{
[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)
{
var author = Author.Lookup(term);

View File

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

View File

@@ -1,9 +1,11 @@
using System.Collections.Generic;
using System.Linq;
using FluentValidation;
using FluentValidation.Results;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Validation;
using NzbDrone.Http.REST.Attributes;
using Readarr.Http;
using Readarr.Http.REST;
@@ -50,6 +52,9 @@ namespace Readarr.Api.V1.CustomFormats
public ActionResult<CustomFormatResource> Create(CustomFormatResource customFormatResource)
{
var model = customFormatResource.ToModel(_specifications);
Validate(model);
return Created(_formatService.Insert(model).Id);
}
@@ -58,6 +63,9 @@ namespace Readarr.Api.V1.CustomFormats
public ActionResult<CustomFormatResource> Update(CustomFormatResource resource)
{
var model = resource.ToModel(_specifications);
Validate(model);
_formatService.Update(model);
return Accepted(model.Id);
@@ -91,6 +99,24 @@ namespace Readarr.Api.V1.CustomFormats
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()
{
yield return new ReleaseTitleSpecification

View File

@@ -69,9 +69,9 @@ namespace Readarr.Api.V1.Queue
}
[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)
{
@@ -80,13 +80,13 @@ namespace Readarr.Api.V1.Queue
}
[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>();
foreach (var id in resource.Ids)
{
var trackedDownload = Remove(id, removeFromClient, blocklist, skipReDownload);
var trackedDownload = Remove(id, removeFromClient, blocklist, skipRedownload);
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);
@@ -238,7 +238,7 @@ namespace Readarr.Api.V1.Queue
if (blocklist)
{
_failedDownloadService.MarkAsFailed(trackedDownload.DownloadItem.DownloadId, skipReDownload);
_failedDownloadService.MarkAsFailed(trackedDownload.DownloadItem.DownloadId, skipRedownload);
}
if (!removeFromClient && !blocklist)

View File

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

View File

@@ -107,7 +107,7 @@ namespace Readarr.Http.ClientSchema
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())
{
@@ -154,33 +154,40 @@ namespace Readarr.Http.ClientSchema
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 value = Convert.ToInt32(v.GetRawConstantValue());
var attrib = v.GetCustomAttribute<FieldOptionAttribute>();
if (attrib != null)
{
return new SelectOption
var options = selectOptions
.GetFields()
.Where(v => v.IsStatic && !v.GetCustomAttributes(false).OfType<ObsoleteAttribute>().Any())
.Select(v =>
{
Value = value,
Name = attrib.Label ?? name,
Order = attrib.Order,
Hint = attrib.Hint ?? $"({value})"
};
}
else
{
return new SelectOption
{
Value = value,
Name = name,
Order = value
};
}
});
var name = v.Name.Replace('_', ' ');
var value = Convert.ToInt32(v.GetRawConstantValue());
var attrib = v.GetCustomAttribute<FieldOptionAttribute>();
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)
@@ -241,7 +248,7 @@ namespace Readarr.Http.ClientSchema
}
else
{
return fieldValue.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
return fieldValue.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => v.Trim());
}
};
}