mirror of
https://github.com/Readarr/Readarr.git
synced 2026-03-13 15:34:06 -04:00
Compare commits
35 Commits
v0.2.4.199
...
sonarr-pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0660b697ed | ||
|
|
704635f758 | ||
|
|
263e807de2 | ||
|
|
9ac9bd25c1 | ||
|
|
4ead5186ae | ||
|
|
dea797c375 | ||
|
|
58ba24762b | ||
|
|
fbd7b4fe33 | ||
|
|
fee7fbbff6 | ||
|
|
18253a298e | ||
|
|
22f92150c3 | ||
|
|
4d7a762ee8 | ||
|
|
b11517e2ac | ||
|
|
d5af254f47 | ||
|
|
f09da06f80 | ||
|
|
d3443510b4 | ||
|
|
d73eb1b5f9 | ||
|
|
39778a95bf | ||
|
|
9fccca1154 | ||
|
|
e165663616 | ||
|
|
b49d2312ab | ||
|
|
52221c7cf4 | ||
|
|
ad7b110a0b | ||
|
|
b04b483f86 | ||
|
|
b79941e0a1 | ||
|
|
84d47b1f23 | ||
|
|
17df4d47fb | ||
|
|
b9f89dddc9 | ||
|
|
e3fc469cd3 | ||
|
|
4304685a65 | ||
|
|
7d77b1fbe5 | ||
|
|
1989174801 | ||
|
|
ac4ae9bb4d | ||
|
|
f399d27470 | ||
|
|
c5fd2e3aa0 |
@@ -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)'
|
||||
|
||||
13
build.sh
13
build.sh
@@ -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
|
||||
|
||||
@@ -91,7 +91,8 @@ module.exports = (env) => {
|
||||
}),
|
||||
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'Content/styles.css'
|
||||
filename: 'Content/styles.css',
|
||||
chunkFilename: 'Content/[id]-[chunkhash].css'
|
||||
}),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)}
|
||||
|
||||
@@ -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
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -33,7 +33,7 @@ class MonitoringOptionsModalContent extends Component {
|
||||
const {
|
||||
isSaving,
|
||||
saveError
|
||||
} = prevProps;
|
||||
} = this.props;
|
||||
|
||||
if (prevProps.isSaving && !isSaving && !saveError) {
|
||||
this.setState({
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
@@ -30,7 +30,7 @@ class BookshelfFooter extends Component {
|
||||
const {
|
||||
isSaving,
|
||||
saveError
|
||||
} = prevProps;
|
||||
} = this.props;
|
||||
|
||||
if (prevProps.isSaving && !isSaving && !saveError) {
|
||||
this.setState({
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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;
|
||||
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Test.Download
|
||||
{
|
||||
AuthorId = 1,
|
||||
BookIds = new List<int> { 1 },
|
||||
SkipReDownload = true
|
||||
SkipRedownload = true
|
||||
};
|
||||
|
||||
Subject.Handle(failedEvent);
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
_definition = new IndexerDefinition
|
||||
{
|
||||
Name = "Indexer",
|
||||
EnableRss = true,
|
||||
ConfigContract = "TorznabSettings",
|
||||
Settings = torznabSettings
|
||||
};
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -614,7 +614,6 @@
|
||||
"StatusEndedDeceased": "Αποθανών",
|
||||
"TrackTitle": "Τίτλος κομματιού",
|
||||
"SkipRedownload": "Παράλειψη επανάληψης λήψης",
|
||||
"SkipredownloadHelpText": "Αποτρέπει το Readarr από το να δοκιμάσει τη λήψη εναλλακτικών εκδόσεων για τα αφαιρεμένα στοιχεία",
|
||||
"CollapseMultipleBooks": "Σύμπτυξη πολλών βιβλίων",
|
||||
"ConvertToFormat": "Μετατροπή σε Μορφοποίηση",
|
||||
"DataNewBooks": "Παρακολουθήστε τα νέα βιβλία που κυκλοφόρησαν μετά το νεότερο υπάρχον βιβλίο",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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": "添加: 添加标签至已有的标签列表中"
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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();
|
||||
|
||||
9
src/NzbDrone.Core/Messaging/Commands/CommandResult.cs
Normal file
9
src/NzbDrone.Core/Messaging/Commands/CommandResult.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace NzbDrone.Core.Messaging.Commands
|
||||
{
|
||||
public enum CommandResult
|
||||
{
|
||||
Unknown = 0,
|
||||
Successful = 1,
|
||||
Unsuccessful = 2
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user