mirror of
https://github.com/Readarr/Readarr.git
synced 2026-03-21 16:54:15 -04:00
Compare commits
43 Commits
v0.2.4.199
...
sonarr-pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03ed01e7d8 | ||
|
|
ce820f6f73 | ||
|
|
53e6cb24b7 | ||
|
|
7c1ca8acc1 | ||
|
|
5e9e578101 | ||
|
|
156407c541 | ||
|
|
1ef6c60318 | ||
|
|
73b3b1848b | ||
|
|
33fbd95707 | ||
|
|
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.2'
|
||||
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}
|
||||
/>
|
||||
|
||||
@@ -6,4 +6,5 @@
|
||||
|
||||
.statusIcon {
|
||||
width: 20px !important;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { tooltipPositions } from 'Helpers/Props';
|
||||
import Tooltip from './Tooltip';
|
||||
import styles from './Popover.css';
|
||||
|
||||
@@ -30,8 +31,13 @@ function Popover(props) {
|
||||
}
|
||||
|
||||
Popover.propTypes = {
|
||||
className: PropTypes.string,
|
||||
bodyClassName: PropTypes.string,
|
||||
anchor: PropTypes.node.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
body: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired
|
||||
body: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
|
||||
position: PropTypes.oneOf(tooltipPositions.all),
|
||||
canFlip: PropTypes.bool
|
||||
};
|
||||
|
||||
export default Popover;
|
||||
|
||||
8
frontend/src/Helpers/Props/TooltipPosition.ts
Normal file
8
frontend/src/Helpers/Props/TooltipPosition.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
enum TooltipPosition {
|
||||
Top = 'top',
|
||||
Right = 'right',
|
||||
Bottom = 'bottom',
|
||||
Left = 'left',
|
||||
}
|
||||
|
||||
export default TooltipPosition;
|
||||
@@ -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}
|
||||
|
||||
@@ -17,6 +17,11 @@ const authenticationMethodOptions = [
|
||||
{ key: 'forms', value: 'Forms (Login Page)' }
|
||||
];
|
||||
|
||||
const authenticationRequiredOptions = [
|
||||
{ key: 'enabled', value: 'Enabled' },
|
||||
{ key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' }
|
||||
];
|
||||
|
||||
const certificateValidationOptions = [
|
||||
{ key: 'enabled', value: 'Enabled' },
|
||||
{ key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' },
|
||||
@@ -68,6 +73,7 @@ class SecuritySettings extends Component {
|
||||
|
||||
const {
|
||||
authenticationMethod,
|
||||
authenticationRequired,
|
||||
username,
|
||||
password,
|
||||
apiKey,
|
||||
@@ -94,7 +100,24 @@ class SecuritySettings extends Component {
|
||||
</FormGroup>
|
||||
|
||||
{
|
||||
authenticationEnabled &&
|
||||
authenticationEnabled ?
|
||||
<FormGroup>
|
||||
<FormLabel>Authentication Required</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="authenticationRequired"
|
||||
values={authenticationRequiredOptions}
|
||||
helpText="Change which requests authentication is required for. Do not change unless you understand the risks."
|
||||
onChange={onInputChange}
|
||||
{...authenticationRequired}
|
||||
/>
|
||||
</FormGroup> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
authenticationEnabled ?
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
{translate('Username')}
|
||||
@@ -106,11 +129,12 @@ class SecuritySettings extends Component {
|
||||
onChange={onInputChange}
|
||||
{...username}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormGroup> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
authenticationEnabled &&
|
||||
authenticationEnabled ?
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
{translate('Password')}
|
||||
@@ -122,7 +146,8 @@ class SecuritySettings extends Component {
|
||||
onChange={onInputChange}
|
||||
{...password}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormGroup> :
|
||||
null
|
||||
}
|
||||
|
||||
<FormGroup>
|
||||
|
||||
@@ -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;
|
||||
@@ -97,6 +97,7 @@
|
||||
"@babel/preset-env": "7.22.9",
|
||||
"@babel/preset-react": "7.22.5",
|
||||
"@babel/preset-typescript": "7.22.5",
|
||||
"@types/redux-actions": "2.6.2",
|
||||
"@typescript-eslint/eslint-plugin": "6.0.0",
|
||||
"@typescript-eslint/parser": "6.0.0",
|
||||
"autoprefixer": "10.4.14",
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace NzbDrone.Automation.Test
|
||||
|
||||
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null);
|
||||
_runner.KillAll();
|
||||
_runner.Start();
|
||||
_runner.Start(true);
|
||||
|
||||
driver.Url = "http://localhost:8787";
|
||||
|
||||
|
||||
@@ -77,7 +77,9 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
FullName = Name;
|
||||
}
|
||||
|
||||
if (IsLinux && File.Exists("/proc/1/cgroup") && File.ReadAllText("/proc/1/cgroup").Contains("/docker/"))
|
||||
if (IsLinux &&
|
||||
((File.Exists("/proc/1/cgroup") && File.ReadAllText("/proc/1/cgroup").Contains("/docker/")) ||
|
||||
(File.Exists("/proc/1/mountinfo") && File.ReadAllText("/proc/1/mountinfo").Contains("/docker/"))))
|
||||
{
|
||||
IsDocker = true;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,13 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
||||
"OutOfMemoryException",
|
||||
|
||||
// Filter out people stuck in boot loops
|
||||
"CorruptDatabaseException"
|
||||
"CorruptDatabaseException",
|
||||
|
||||
// Filter SingleInstance Termination Exceptions
|
||||
"TerminateApplicationException",
|
||||
|
||||
// User config issue, root folder missing, etc.
|
||||
"DirectoryNotFoundException"
|
||||
};
|
||||
|
||||
public static readonly List<string> FilteredExceptionMessages = new List<string>
|
||||
|
||||
@@ -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,8 @@
|
||||
namespace NzbDrone.Core.Authentication
|
||||
{
|
||||
public enum AuthenticationRequiredType
|
||||
{
|
||||
Enabled = 0,
|
||||
DisabledForLocalAddresses = 1
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
namespace NzbDrone.Core.Authentication
|
||||
namespace NzbDrone.Core.Authentication
|
||||
{
|
||||
public enum AuthenticationType
|
||||
{
|
||||
None = 0,
|
||||
Basic = 1,
|
||||
Forms = 2
|
||||
Forms = 2,
|
||||
External = 3
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace NzbDrone.Core.Configuration
|
||||
bool EnableSsl { get; }
|
||||
bool LaunchBrowser { get; }
|
||||
AuthenticationType AuthenticationMethod { get; }
|
||||
AuthenticationRequiredType AuthenticationRequired { get; }
|
||||
bool AnalyticsEnabled { get; }
|
||||
string LogLevel { get; }
|
||||
string ConsoleLogLevel { get; }
|
||||
@@ -190,6 +191,8 @@ namespace NzbDrone.Core.Configuration
|
||||
}
|
||||
}
|
||||
|
||||
public AuthenticationRequiredType AuthenticationRequired => GetValueEnum("AuthenticationRequired", AuthenticationRequiredType.Enabled);
|
||||
|
||||
public bool AnalyticsEnabled => GetValueBoolean("AnalyticsEnabled", true, persist: false);
|
||||
|
||||
// TODO: Change back to "master" for the first stable release
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.ThingiProvider.Events;
|
||||
|
||||
namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
[CheckOn(typeof(ProviderUpdatedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderDeletedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderUpdatedEvent<IDownloadClient>))]
|
||||
[CheckOn(typeof(ProviderDeletedEvent<IDownloadClient>))]
|
||||
public class IndexerDownloadClientCheck : HealthCheckBase
|
||||
{
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
private readonly IDownloadClientFactory _downloadClientFactory;
|
||||
|
||||
public IndexerDownloadClientCheck(IIndexerFactory indexerFactory,
|
||||
IDownloadClientFactory downloadClientFactory,
|
||||
ILocalizationService localizationService)
|
||||
: base(localizationService)
|
||||
{
|
||||
_indexerFactory = indexerFactory;
|
||||
_downloadClientFactory = downloadClientFactory;
|
||||
}
|
||||
|
||||
public override HealthCheck Check()
|
||||
{
|
||||
var downloadClientsIds = _downloadClientFactory.All().Where(v => v.Enable).Select(v => v.Id).ToList();
|
||||
var invalidIndexers = _indexerFactory.All()
|
||||
.Where(v => v.Enable && v.DownloadClientId > 0 && !downloadClientsIds.Contains(v.DownloadClientId))
|
||||
.ToList();
|
||||
|
||||
if (invalidIndexers.Any())
|
||||
{
|
||||
return new HealthCheck(GetType(),
|
||||
HealthCheckResult.Warning,
|
||||
string.Format(_localizationService.GetLocalizedString("IndexerDownloadClientHealthCheckMessage"), string.Join(", ", invalidIndexers.Select(v => v.Name).ToArray())),
|
||||
"#invalid-indexer-download-client-setting");
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": "Παρακολουθήστε τα νέα βιβλία που κυκλοφόρησαν μετά το νεότερο υπάρχον βιβλίο",
|
||||
|
||||
@@ -392,6 +392,7 @@
|
||||
"IncludeUnknownAuthorItemsHelpText": "Show items without a author in the queue, this could include removed authors, books or anything else in Readarr's category",
|
||||
"IncludeUnmonitored": "Include Unmonitored",
|
||||
"Indexer": "Indexer",
|
||||
"IndexerDownloadClientHealthCheckMessage": "Indexers with invalid download clients: {0}.",
|
||||
"IndexerDownloadClientHelpText": "Specify which download client is used for grabs from this indexer",
|
||||
"IndexerIdHelpText": "Specify what indexer the profile applies to",
|
||||
"IndexerIdHelpTextWarning": "Using a specific indexer with preferred words can lead to duplicate releases being grabbed",
|
||||
@@ -825,8 +826,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.",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"SslPortHelpTextWarning": "Nécessite un redémarrage pour prendre effet",
|
||||
"SslCertPathHelpTextWarning": "Nécessite un redémarrage pour prendre effet",
|
||||
"UnableToLoadMetadataProfiles": "Impossible de charger les profils de délai",
|
||||
"AddingTag": "Ajout d'un tag",
|
||||
"AddingTag": "Ajouter un tag",
|
||||
"AgeWhenGrabbed": "Age (au moment du téléchargement)",
|
||||
"AlreadyInYourLibrary": "Déjà présent dans votre collection",
|
||||
"AlternateTitles": "Titre alternatif",
|
||||
@@ -761,5 +761,8 @@
|
||||
"ExistingTag": "Tag existant",
|
||||
"No": "Non",
|
||||
"RemovingTag": "Suppression du tag",
|
||||
"SetTags": "Définir Tags"
|
||||
"SetTags": "Définir Tags",
|
||||
"CountDownloadClientsSelected": "{0} client(s) de téléchargement sélectionné(s)",
|
||||
"EditSelectedDownloadClients": "Modifier les clients de téléchargement sélectionnés",
|
||||
"EditSelectedIndexers": "Modifier les indexeurs sélectionnés"
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
"Theme": "Tema",
|
||||
"Title": "Naslov",
|
||||
"Torrents": "Torrenti",
|
||||
"UI": "Korisničko Sučelje",
|
||||
"UI": "Korisničko sučelje",
|
||||
"URLBase": "URL Base",
|
||||
"Usenet": "Usenet",
|
||||
"Analytics": "Analitika",
|
||||
@@ -155,5 +155,29 @@
|
||||
"DeleteSelectedIndexersMessageText": "Jeste li sigurni da želite obrisati oznaku formata {0}?",
|
||||
"RemoveSelectedItemQueueMessageText": "Jeste li sigurni da želite izbrisati stavku {0} iz reda?",
|
||||
"RemoveSelectedItemsQueueMessageText": "Jeste li sigurni da želite izbrisati stavku {0} iz reda?",
|
||||
"Required": "Zahtjevaj"
|
||||
"Required": "Zahtjevaj",
|
||||
"Options": "Opcije",
|
||||
"Style": "Stil",
|
||||
"Today": "Danas",
|
||||
"UnselectAll": "Odznači sve",
|
||||
"No": "Ne",
|
||||
"Restart": "Resetiraj",
|
||||
"RestartNow": "Resetiraj sad",
|
||||
"Tomorrow": "Sutra",
|
||||
"Yes": "Da",
|
||||
"History": "Povijest",
|
||||
"Save": "Spremi",
|
||||
"Security": "Sigurnost",
|
||||
"SelectAll": "Odaberi sve",
|
||||
"Source": "Izvor",
|
||||
"Status": "Status",
|
||||
"Time": "Vrijeme",
|
||||
"YesCancel": "Da, otkaži",
|
||||
"Yesterday": "Jučer",
|
||||
"Year": "Godina",
|
||||
"Result": "Rezultat",
|
||||
"UseProxy": "Koristi proxy",
|
||||
"RemoveFilter": "Ukloni filter",
|
||||
"Name": "Ime",
|
||||
"Version": "Verzija"
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -147,5 +147,7 @@
|
||||
"AddMissing": "Legg til manglende",
|
||||
"AddNewItem": "Legg til nytt item",
|
||||
"45MinutesFourtyFive": "45 Minutter: {0}",
|
||||
"60MinutesSixty": "60 Minutter: {0}"
|
||||
"60MinutesSixty": "60 Minutter: {0}",
|
||||
"ApplyChanges": "Bekreft endringer",
|
||||
"ApiKeyValidationHealthCheckMessage": "Vennligst oppdater din API-nøkkel til å være minst {0} tegn lang. Du kan gjøre dette via innstillinger eller konfigurasjonsfilen"
|
||||
}
|
||||
|
||||
@@ -17,14 +17,14 @@
|
||||
"APIKey": "API-sleutel",
|
||||
"About": "Over",
|
||||
"AddListExclusion": "Toevoegen aan Uitzonderingenlijst",
|
||||
"AddingTag": "Tag toevoegen",
|
||||
"AddingTag": "Tag wordt toegevoegd",
|
||||
"AgeWhenGrabbed": "Leeftijd (op moment van ophalen)",
|
||||
"AlreadyInYourLibrary": "Reeds in uw bibliotheek",
|
||||
"AlternateTitles": "Alternatieve Titel",
|
||||
"Analytics": "Statistieken",
|
||||
"AnalyticsEnabledHelpText": "Stuur anonieme gebruiks- en foutinformatie naar de servers van Radarr. Dit omvat informatie over uw browser, welke Radarr WebUI pagina's u gebruikt, foutrapportage en OS en runtime versie. We zullen deze informatie gebruiken om prioriteiten te stellen voor functies en het verhelpen van fouten.",
|
||||
"AppDataDirectory": "AppData map",
|
||||
"ApplyTags": "Tags Toepassen",
|
||||
"AppDataDirectory": "AppData folder",
|
||||
"ApplyTags": "Pas Tags Toe",
|
||||
"Authentication": "Authenticatie",
|
||||
"AuthenticationMethodHelpText": "Gebruikersnaam en wachtwoord nodig voor toegang tot Radarr",
|
||||
"AuthorClickToChangeBook": "Klik om film te wijzigen",
|
||||
@@ -32,7 +32,7 @@
|
||||
"AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Verwijderde films zullen automatisch als onbewaakt worden gemarkeerd in Radarr",
|
||||
"Automatic": "Automatisch",
|
||||
"BackupFolderHelpText": "Relatieve paden zullen t.o.v. de Radarr AppData map bekeken worden",
|
||||
"BackupNow": "Veiligheidskopie Maken",
|
||||
"BackupNow": "Nu backup nemen",
|
||||
"BackupRetentionHelpText": "Automatische veiligheidskopieën ouder dan de retentie periode zullen worden opgeruimd",
|
||||
"Backups": "Veiligheidskopieën",
|
||||
"BindAddress": "Gebonden Adres",
|
||||
@@ -55,7 +55,7 @@
|
||||
"ChmodFolderHelpTextWarning": "Dit werkt alleen als de gebruiker die Radarr draait de eigenaar is van het bestand. Het is beter om zeker te zijn dat de downloader de juiste rechten zet.",
|
||||
"ChownGroupHelpText": "Groep naam of gid. Gebruik gid voor externe bestandssystemen.",
|
||||
"ChownGroupHelpTextWarning": "Dit werkt alleen als de gebruiker die Radarr draait de eigenaar is van het bestand. Het is beter om zeker te zijn dat de downloader dezelfde groep gebruikt als Radarr.",
|
||||
"Clear": "Wissen",
|
||||
"Clear": "Wis",
|
||||
"ClickToChangeQuality": "Klik om kwaliteit te wijzigen",
|
||||
"ClientPriority": "Client Prioriteit",
|
||||
"CloneIndexer": "Dupliceer Indexeerder",
|
||||
@@ -76,8 +76,8 @@
|
||||
"DelayProfile": "Vertragingsprofiel",
|
||||
"DelayProfiles": "Vertragingsprofielen",
|
||||
"DelayingDownloadUntilInterp": "Vertraag download tot {0} op {1}",
|
||||
"Delete": "Verwijderen",
|
||||
"DeleteBackup": "Verwijder Veiligheidskopie",
|
||||
"Delete": "Verwijder",
|
||||
"DeleteBackup": "Verwijder Backup",
|
||||
"DeleteBackupMessageText": "Bent u zeker dat u de veiligheidskopie '{0}' wilt verwijderen?",
|
||||
"DeleteDelayProfile": "Verwijder Vertragingsprofiel",
|
||||
"DeleteDelayProfileMessageText": "Weet u zeker dat u dit vertragingsprofiel wilt verwijderen?",
|
||||
@@ -458,8 +458,8 @@
|
||||
"ShowBookTitleHelpText": "Toon filmtitel onder poster",
|
||||
"Blocklist": "Blokkeerlijst",
|
||||
"BlocklistRelease": "Uitgave op blokkeerlijst zetten",
|
||||
"All": "Alles",
|
||||
"Component": "Onderdeel",
|
||||
"All": "Alle",
|
||||
"Component": "Component",
|
||||
"ConsoleLogLevel": "Console-logboekniveau",
|
||||
"DeleteBookFileMessageText": "Weet u zeker dat u {0} wilt verwijderen?",
|
||||
"FilterAnalyticsEvents": "Analytics-gebeurtenissen filteren",
|
||||
@@ -637,5 +637,15 @@
|
||||
"ThereWasAnErrorLoadingThisPage": "Er ging iets fout bij het laden van deze pagina",
|
||||
"ApiKeyValidationHealthCheckMessage": "Maak je API sleutel alsjeblieft minimaal {0} karakters lang. Dit kan gedaan worden via de instellingen of het configuratiebestand",
|
||||
"ResetDefinitions": "Reset definities",
|
||||
"ResetTitles": "Reset titels"
|
||||
"ResetTitles": "Reset titels",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Hoe tags toepassen op de geselecteerde indexeerders",
|
||||
"AutomaticAdd": "Automatisch Toevoegen",
|
||||
"CloneCondition": "Kloon Conditie",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "Hoe tags toepassen op de geselecteerde import lijsten",
|
||||
"ApplyChanges": "Pas Wijzigingen Toe",
|
||||
"ApplyTagsHelpTextAdd": "Toevoegen: Voeg de tags toe aan de bestaande tag lijst",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Hoe tags toepassen op de geselecteerde download clients",
|
||||
"ApplyTagsHelpTextRemove": "Verwijderen: Verwijder de ingevoerde tags",
|
||||
"ApplyTagsHelpTextReplace": "Vervangen: Vervang de tags met de ingevoerde tags (vul geen tags in om alle tags te wissen)",
|
||||
"AutoAdd": "Automatisch Toevoegen"
|
||||
}
|
||||
|
||||
@@ -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,16 +37,16 @@
|
||||
"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",
|
||||
"DownloadClient": "Cliente de Download",
|
||||
"DownloadClientSettings": "Configurações do cliente de download",
|
||||
"DownloadClients": "Clientes de download",
|
||||
"DownloadFailedCheckDownloadClientForMoreDetails": "Falha no download: verifique o cliente de download para saber mais",
|
||||
@@ -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?",
|
||||
@@ -127,7 +127,7 @@
|
||||
"DeleteQualityProfileMessageText": "Tem certeza que deseja excluir o perfil de qualidade \"{0}\"?",
|
||||
"DeleteReleaseProfile": "Excluir Perfil de Lançamento",
|
||||
"DeleteReleaseProfileMessageText": "Tem certeza de que deseja excluir este Perfil de Lançamento?",
|
||||
"DeleteRootFolderMessageText": "Tem certeza de que deseja excluir a pasta raiz \"{0}\"?",
|
||||
"DeleteRootFolderMessageText": "Tem certeza de que deseja excluir a pasta raiz '{0}'?",
|
||||
"DeleteSelectedBookFiles": "Excluir arquivos do livro selecionado",
|
||||
"DeleteSelectedBookFilesMessageText": "Tem certeza de que deseja excluir os arquivos do livro selecionado?",
|
||||
"DeleteTag": "Excluir tag",
|
||||
@@ -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",
|
||||
@@ -183,7 +183,7 @@
|
||||
"Importing": "Importando",
|
||||
"IncludeHealthWarningsHelpText": "Incluir avisos de integridade",
|
||||
"IncludeUnknownAuthorItemsHelpText": "Mostrar itens sem autor na fila, isso pode incluir autores removidos, livros ou qualquer outra coisa na categoria de Readarr",
|
||||
"IncludeUnmonitored": "Incluir não monitorados",
|
||||
"IncludeUnmonitored": "Incluir não monitorado",
|
||||
"Indexer": "Indexador",
|
||||
"IndexerPriority": "Prioridade do indexador",
|
||||
"IndexerSettings": "Configurações do indexador",
|
||||
@@ -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",
|
||||
@@ -319,7 +319,7 @@
|
||||
"RetentionHelpText": "Apenas Usenet: use 0 para definir para retenção ilimitada",
|
||||
"RetryingDownloadInterp": "Tentando novamente o download {0} em {1}",
|
||||
"RootFolder": "Pasta Raiz",
|
||||
"RootFolders": "Pastas raiz",
|
||||
"RootFolders": "Pastas Raiz",
|
||||
"RssSyncIntervalHelpText": "Intervalo em minutos. Defina como zero para desabilitar (isso interromperá todos os downloads automáticos de lançamentos)",
|
||||
"SSLCertPassword": "Senha do certificado SSL",
|
||||
"SSLCertPath": "Caminho do certificado SSL",
|
||||
@@ -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",
|
||||
@@ -589,7 +588,7 @@
|
||||
"DiscNumber": "Número do disco",
|
||||
"DiscCount": "Contagem de disco",
|
||||
"Development": "Desenvolvimento",
|
||||
"DeleteRootFolder": "Excluir pasta raiz",
|
||||
"DeleteRootFolder": "Excluir Pasta Raiz",
|
||||
"DeleteMetadataProfile": "Excluir perfil de metadados",
|
||||
"DeleteImportList": "Excluir lista de importação",
|
||||
"DeleteFilesHelpText": "Excluir arquivos do livro e pasta do autor",
|
||||
@@ -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",
|
||||
@@ -827,7 +826,7 @@
|
||||
"SettingsRemotePathMappingRemotePathHelpText": "Caminho raiz para o diretório que o Cliente de Download acessa",
|
||||
"SizeLimit": "Tamanho limite",
|
||||
"SystemTimeCheckMessage": "A hora do sistema está desligada por mais de 1 dia. Tarefas agendadas podem não ser executadas corretamente até que o horário seja corrigido",
|
||||
"TimeLeft": "Tempo restante",
|
||||
"TimeLeft": "Tempo Restante",
|
||||
"UISettingsSummary": "Opções de calendário, data e cores prejudicadas",
|
||||
"UpdateCheckStartupNotWritableMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' não pode ser gravada pelo usuário '{1}'.",
|
||||
"UpdateCheckStartupTranslocationMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' está em uma pasta de translocação de aplicativo.",
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"DiskSpace": "Spațiul pe disc",
|
||||
"EnableRSS": "Activați RSS",
|
||||
"EnableSslHelpText": " Necesită repornirea în funcție de administrator pentru a intra în vigoare",
|
||||
"Ended": "Finalizat",
|
||||
"Ended": "Încheiat",
|
||||
"Grab": "Apuca",
|
||||
"LoadingBookFilesFailed": "Încărcarea fișierelor film a eșuat",
|
||||
"LoadingBooksFailed": "Încărcarea fișierelor film a eșuat",
|
||||
@@ -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.",
|
||||
@@ -138,7 +138,7 @@
|
||||
"DelayProfiles": "Profile de întârziere",
|
||||
"DelayingDownloadUntilInterp": "Întârzierea descărcării până la {0} la {1}",
|
||||
"Delete": "Șterge",
|
||||
"DeleteBackup": "Ștergeți copie de siguranță",
|
||||
"DeleteBackup": "Ștergeți copia de rezervă",
|
||||
"DeleteBackupMessageText": "Sigur doriți să ștergeți copia de siguranță „{0}”?",
|
||||
"DeleteDelayProfile": "Ștergeți profilul de întârziere",
|
||||
"DeleteDelayProfileMessageText": "Sigur doriți să ștergeți acest profil de întârziere?",
|
||||
@@ -192,7 +192,7 @@
|
||||
"FileDateHelpText": "Schimbați data fișierului la import / rescanare",
|
||||
"FileManagement": "Administrarea de fișiere",
|
||||
"FileNames": "Numele fișierelor",
|
||||
"Filename": "Numele fișierului",
|
||||
"Filename": "Nume fișier",
|
||||
"Files": "Fișiere",
|
||||
"FirstDayOfWeek": "Prima zi a săptămânii",
|
||||
"Fixed": "Fix",
|
||||
@@ -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",
|
||||
@@ -546,7 +546,7 @@
|
||||
"RestartRequiredHelpTextWarning": "Necesită repornire pentru a intra în vigoare",
|
||||
"Test": "Testează",
|
||||
"AddList": "Adaugă listă",
|
||||
"RenameFiles": "Redenumește Fișiere",
|
||||
"RenameFiles": "Redenumește Fișierele",
|
||||
"Label": "Etichetă",
|
||||
"ImportListExclusions": "Ștergeți excluderea listei de import",
|
||||
"ManualImportSelectEdition": "Import manual - Selectați film",
|
||||
@@ -571,7 +571,7 @@
|
||||
"CutoffFormatScoreHelpText": "Odată ce acest scor personalizat este atins, Radarr nu va mai descărca filme",
|
||||
"DeleteFormatMessageText": "Sigur doriți să ștergeți eticheta format {0}?",
|
||||
"IncludeCustomFormatWhenRenamingHelpText": "Includeți în formatul de redenumire {Formate personalizate}",
|
||||
"HiddenClickToShow": "Ascuns, faceți clic pentru a afișa",
|
||||
"HiddenClickToShow": "Ascuns, faceți clic pentru afișare",
|
||||
"HideAdvanced": "Ascunde Avansat",
|
||||
"ShowAdvanced": "Arată setări avansate",
|
||||
"ShownClickToHide": "Afișat, faceți clic pentru a ascunde",
|
||||
@@ -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",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Cum se aplică etichete filmelor selectate",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "Cum se aplică etichete listelor de import selectate",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Cum se aplică etichete clienților de descărcare selectați",
|
||||
"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.",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"BackupFolderHelpText": "相对路径将在Radarr的AppData目录下",
|
||||
"BackupNow": "马上备份",
|
||||
"BackupRetentionHelpText": "早于保留周期的自动备份将被自动清除",
|
||||
"Backups": "备份",
|
||||
"Backups": "历史备份",
|
||||
"BindAddress": "绑定地址",
|
||||
"BindAddressHelpTextWarning": "需重启以生效",
|
||||
"BookIsDownloading": "影片正在下载中",
|
||||
@@ -422,7 +422,7 @@
|
||||
"Version": "版本",
|
||||
"WeekColumnHeader": "日期格式",
|
||||
"Year": "年",
|
||||
"YesCancel": "是,取消",
|
||||
"YesCancel": "确定,取消",
|
||||
"20MinutesTwenty": "20 分钟:{0}",
|
||||
"45MinutesFourtyFive": "45分钟: {0}",
|
||||
"60MinutesSixty": "60分钟: {0}",
|
||||
@@ -438,13 +438,13 @@
|
||||
"MaintenanceRelease": "维护版本:修复错误及其他改进,参见Github提交 查看更多详情",
|
||||
"DeleteBookFileMessageText": "您确认您想删除吗?",
|
||||
"ApiKeyHelpTextWarning": "需重启以生效",
|
||||
"Actions": "操作",
|
||||
"Actions": "动作",
|
||||
"AddMissing": "添加丢失项",
|
||||
"AddNewItem": "添加新项目",
|
||||
"DeleteRootFolderMessageText": "您确定要删除索引 '{0}'吗?",
|
||||
"Progress": "进度",
|
||||
"Publisher": "发布者",
|
||||
"Series": "集",
|
||||
"Series": "节目",
|
||||
"ShowBookTitleHelpText": "在海报下显示电影标题",
|
||||
"BookAvailableButMissing": "影片可下载,但没有下载",
|
||||
"ShowReleaseDate": "显示发布日期",
|
||||
@@ -454,7 +454,7 @@
|
||||
"NotAvailable": "不可用",
|
||||
"NotMonitored": "未监控的",
|
||||
"OutputPath": "输出路径",
|
||||
"ReleaseTitle": "歌曲发布标题",
|
||||
"ReleaseTitle": "发布标题",
|
||||
"TheAuthorFolderAndAllOfItsContentWillBeDeleted": "电影目录 '{0}' 及所有内容都会被删除。",
|
||||
"Today": "今天",
|
||||
"Tomorrow": "明天",
|
||||
@@ -480,14 +480,14 @@
|
||||
"All": "全部",
|
||||
"Level": "等级",
|
||||
"RemoveFromBlocklist": "从黑名单中移除",
|
||||
"Blocklist": "阻止列表",
|
||||
"Blocklist": "黑名单",
|
||||
"BlocklistRelease": "黑名单版本",
|
||||
"AllowFingerprinting": "允许指纹识别",
|
||||
"AllExpandedCollapseAll": "收缩所有",
|
||||
"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,33 @@
|
||||
"RemoveSelectedItemQueueMessageText": "您确定要从队列中删除 1 项吗?",
|
||||
"RemoveSelectedItemBlocklistMessageText": "您确定要从阻止列表中删除所选项目吗?",
|
||||
"RemoveSelectedItemsQueueMessageText": "您确定要从队列中删除 {0} 个项目吗?",
|
||||
"ResetQualityDefinitionsMessageText": "您确定要重置质量定义吗?"
|
||||
"ResetQualityDefinitionsMessageText": "您确定要重置质量定义吗?",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "如何将标签应用到已选择的下载客户端",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "如何将标签应用到已选择的导入列表",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "如何将标签应用到已选择的索引器",
|
||||
"ApplyTagsHelpTextRemove": "移除: 移除已输入的标签",
|
||||
"ApplyTagsHelpTextReplace": "替换: 用输入的标签替换当前标签 (不输入将会清除所有标签)",
|
||||
"ApplyTagsHelpTextAdd": "添加: 添加标签至已有的标签列表中",
|
||||
"AutoAdd": "自动添加",
|
||||
"DeleteSelectedIndexers": "删除索引器",
|
||||
"EditSelectedDownloadClients": "编辑选定的下载客户端",
|
||||
"EditSelectedImportLists": "编辑选定的导入列表",
|
||||
"EditSelectedIndexers": "编辑选定的索引器",
|
||||
"ExistingTag": "已有标签",
|
||||
"NoEventsFound": "无事件",
|
||||
"RemoveCompleted": "移除已完成",
|
||||
"RemoveSelectedItems": "删除所选项目",
|
||||
"RemovingTag": "移除标签",
|
||||
"SetTags": "设置标签",
|
||||
"Yes": "确定",
|
||||
"BlocklistReleases": "黑名单版本",
|
||||
"Required": "必须的",
|
||||
"RemoveFailed": "删除失败",
|
||||
"RemoveSelectedItem": "删除所选项目",
|
||||
"DeleteSelectedDownloadClients": "删除下载客户端",
|
||||
"DeleteSelectedImportLists": "删除导入列表",
|
||||
"NoChange": "无修改",
|
||||
"Negated": "无效的",
|
||||
"No": "否",
|
||||
"ResetQualityDefinitions": "重置质量定义"
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.TPL;
|
||||
using NzbDrone.Core.Configuration;
|
||||
@@ -116,6 +117,9 @@ namespace NzbDrone.Core.MediaFiles
|
||||
|
||||
private void StartWatchingPath(string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsNotNullOrWhiteSpace();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
// Already being watched
|
||||
if (_fileSystemWatchers.ContainsKey(path))
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,6 +171,8 @@ namespace NzbDrone.Host
|
||||
.PersistKeysToFileSystem(new DirectoryInfo(Configuration["dataProtectionFolder"]));
|
||||
|
||||
services.AddSingleton<IAuthorizationPolicyProvider, UiAuthorizationPolicyProvider>();
|
||||
services.AddSingleton<IAuthorizationHandler, UiAuthorizationHandler>();
|
||||
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("SignalR", policy =>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -38,12 +38,12 @@ namespace NzbDrone.Test.Common
|
||||
Port = port;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
public void Start(bool enableAuth = false)
|
||||
{
|
||||
AppData = Path.Combine(TestContext.CurrentContext.TestDirectory, "_intg_" + TestBase.GetUID());
|
||||
Directory.CreateDirectory(AppData);
|
||||
|
||||
GenerateConfigFile();
|
||||
GenerateConfigFile(enableAuth);
|
||||
|
||||
string readarrConsoleExe;
|
||||
if (OsInfo.IsWindows)
|
||||
@@ -178,7 +178,7 @@ namespace NzbDrone.Test.Common
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateConfigFile()
|
||||
private void GenerateConfigFile(bool enableAuth)
|
||||
{
|
||||
var configFile = Path.Combine(AppData, "config.xml");
|
||||
|
||||
@@ -191,6 +191,8 @@ namespace NzbDrone.Test.Common
|
||||
new XElement(nameof(ConfigFileProvider.ApiKey), apiKey),
|
||||
new XElement(nameof(ConfigFileProvider.LogLevel), "trace"),
|
||||
new XElement(nameof(ConfigFileProvider.AnalyticsEnabled), false),
|
||||
new XElement(nameof(ConfigFileProvider.AuthenticationMethod), enableAuth ? "Forms" : "None"),
|
||||
new XElement(nameof(ConfigFileProvider.AuthenticationRequired), "DisabledForLocalAddresses"),
|
||||
new XElement(nameof(ConfigFileProvider.Port), Port)));
|
||||
|
||||
var data = xDoc.ToString();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Readarr.Api.V1.Config
|
||||
public bool EnableSsl { get; set; }
|
||||
public bool LaunchBrowser { get; set; }
|
||||
public AuthenticationType AuthenticationMethod { get; set; }
|
||||
public AuthenticationRequiredType AuthenticationRequired { get; set; }
|
||||
public bool AnalyticsEnabled { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
@@ -57,6 +58,7 @@ namespace Readarr.Api.V1.Config
|
||||
EnableSsl = model.EnableSsl,
|
||||
LaunchBrowser = model.LaunchBrowser,
|
||||
AuthenticationMethod = model.AuthenticationMethod,
|
||||
AuthenticationRequired = model.AuthenticationRequired,
|
||||
AnalyticsEnabled = model.AnalyticsEnabled,
|
||||
|
||||
//Username
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Readarr.Http.Authentication
|
||||
public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions
|
||||
{
|
||||
public const string DefaultScheme = "API Key";
|
||||
|
||||
public string Scheme => DefaultScheme;
|
||||
public string AuthenticationType = DefaultScheme;
|
||||
|
||||
|
||||
@@ -22,10 +22,16 @@ namespace Readarr.Http.Authentication
|
||||
return authenticationBuilder.AddScheme<AuthenticationSchemeOptions, NoAuthenticationHandler>(name, options => { });
|
||||
}
|
||||
|
||||
public static AuthenticationBuilder AddExternal(this AuthenticationBuilder authenticationBuilder, string name)
|
||||
{
|
||||
return authenticationBuilder.AddScheme<AuthenticationSchemeOptions, NoAuthenticationHandler>(name, options => { });
|
||||
}
|
||||
|
||||
public static AuthenticationBuilder AddAppAuthentication(this IServiceCollection services)
|
||||
{
|
||||
return services.AddAuthentication()
|
||||
.AddNone(AuthenticationType.None.ToString())
|
||||
.AddExternal(AuthenticationType.External.ToString())
|
||||
.AddBasic(AuthenticationType.Basic.ToString())
|
||||
.AddCookie(AuthenticationType.Forms.ToString(), options =>
|
||||
{
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using Microsoft.AspNetCore.Authorization.Infrastructure;
|
||||
|
||||
namespace NzbDrone.Http.Authentication
|
||||
{
|
||||
public class BypassableDenyAnonymousAuthorizationRequirement : DenyAnonymousAuthorizationRequirement
|
||||
{
|
||||
}
|
||||
}
|
||||
45
src/Readarr.Http/Authentication/UiAuthorizationHandler.cs
Normal file
45
src/Readarr.Http/Authentication/UiAuthorizationHandler.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Authentication;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Configuration.Events;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using Readarr.Http.Extensions;
|
||||
|
||||
namespace NzbDrone.Http.Authentication
|
||||
{
|
||||
public class UiAuthorizationHandler : AuthorizationHandler<BypassableDenyAnonymousAuthorizationRequirement>, IAuthorizationRequirement, IHandle<ConfigSavedEvent>
|
||||
{
|
||||
private readonly IConfigFileProvider _configService;
|
||||
private static AuthenticationRequiredType _authenticationRequired;
|
||||
|
||||
public UiAuthorizationHandler(IConfigFileProvider configService)
|
||||
{
|
||||
_configService = configService;
|
||||
_authenticationRequired = configService.AuthenticationRequired;
|
||||
}
|
||||
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, BypassableDenyAnonymousAuthorizationRequirement requirement)
|
||||
{
|
||||
if (_authenticationRequired == AuthenticationRequiredType.DisabledForLocalAddresses)
|
||||
{
|
||||
if (context.Resource is HttpContext httpContext &&
|
||||
IPAddress.TryParse(httpContext.GetRemoteIP(), out var ipAddress) &&
|
||||
ipAddress.IsLocalAddress())
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Handle(ConfigSavedEvent message)
|
||||
{
|
||||
_authenticationRequired = _configService.AuthenticationRequired;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@ namespace NzbDrone.Http.Authentication
|
||||
if (policyName.Equals(POLICY_NAME, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var policy = new AuthorizationPolicyBuilder(_config.AuthenticationMethod.ToString())
|
||||
.RequireAuthenticatedUser();
|
||||
.AddRequirements(new BypassableDenyAnonymousAuthorizationRequirement());
|
||||
|
||||
return Task.FromResult(policy.Build());
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1508,6 +1508,11 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/redux-actions@2.6.2":
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/redux-actions/-/redux-actions-2.6.2.tgz#5956d9e7b9a644358e2c0610f47b1fa3060edc21"
|
||||
integrity sha512-TvcINy8rWFANcpc3EiEQX9Yv3owM3d3KIrqr2ryUIOhYIYzXA/bhDZeGSSSuai62iVR2qMZUgz9tQ5kr0Kl+Tg==
|
||||
|
||||
"@types/scheduler@*":
|
||||
version "0.16.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5"
|
||||
|
||||
Reference in New Issue
Block a user