1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-28 18:05:41 -04:00

Compare commits

...

35 Commits

Author SHA1 Message Date
Bogdan
4eb4128a89 Remove BOM from download clients 2024-01-28 00:01:00 +02:00
Stevie Robinson
f90cdbb112 Translate Download Clients on the backend
(cherry picked from commit 48b12f5b00429a7cd218d23f0544641b0da62a06)

Closes #9391
2024-01-27 23:49:48 +02:00
Stevie Robinson
a8dbc97921 Fixed: Validating DownloadStation output path
(cherry picked from commit 07cbd7c8d29f2e0731f0a11c685f898f7e83b0c8)

Closes #9695
2024-01-27 18:38:44 +02:00
Bogdan
f93e136386 Use string interpolation for test string
Closes #9696
2024-01-27 18:13:37 +02:00
Bogdan
a70fa0fcfe Fix improve parsing logging 2024-01-27 17:58:43 +02:00
Stevie Robinson
c8931784a7 Translate Calendar Frontend
(cherry picked from commit bf43453c040ba0474e0cd71e1c5aa3b98edf13ff)

Closes #8984
2024-01-27 17:57:38 +02:00
Weblate
f601448a65 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Crocmou <slaanesh8854@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Lars <lars.erik.heloe@gmail.com>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: diaverso <alexito_perez.95@hotmail.com>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2024-01-27 17:37:43 +02:00
bakerboy448
64125a31b6 Improve Parsing Logging 2024-01-27 17:37:17 +02:00
Weblate
2f4da90d8a Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Crocmou <slaanesh8854@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: diaverso <alexito_perez.95@hotmail.com>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2024-01-27 16:36:08 +02:00
Bogdan
20d9db2cde New: Date added for files in movie details 2024-01-27 13:18:13 +02:00
Mark McDowall
5b7c0a94fb Fixed: History retention for Newsbin
(cherry picked from commit 0ea189d03c8c5e02c00b96a4281dd9e668d6a9ae)
2024-01-27 10:16:38 +02:00
Servarr
1416f7898e Automated API Docs update 2024-01-27 10:16:21 +02:00
Bogdan
f9cd9f3204 Language comment in Manual Import
Closes #9612
2024-01-26 11:58:51 +02:00
Mark McDowall
99ab65f790 New: Add recycle bin path for deleted movies to webhook/custom script
(cherry picked from commit a71d40edba1388d67e4deefd8bfc354a7a83c6b1)

Closes #9674
2024-01-26 11:44:41 +02:00
Bogdan
82fb355930 New: External link to TMDb for Cast/Crew
Fixes #9667
Closes #9668
2024-01-24 14:03:56 +02:00
Servarr
83d437cbb3 Automated API Docs update 2024-01-24 12:28:42 +02:00
Bogdan
4beb5b328b New: Option to disable Email encryption
* New: Option to disable Email encryption

(cherry picked from commit 7be5732a3a6679120b0f01bd1eb1207194f57f5e)

* Fix possible NullRef in Email Encryption migration

(cherry picked from commit 271266b10ac51ee6dd7a7024d346b631bd5397c2)

---------

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2024-01-24 12:11:51 +02:00
Bogdan
23830f50ac Fixed: Testing for disabled Notifications 2024-01-24 12:10:57 +02:00
Weblate
b808a92cdf Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translation: Servarr/Radarr
2024-01-24 09:41:04 +02:00
Mark McDowall
3185c73659 New: Optionally remove from queue by changing category to 'Post-Import Category' when configured
(cherry picked from commit 345854d0fe9b65a561fdab12aac688782a420aa5)

Closes #9680
2024-01-24 09:37:39 +02:00
Weblate
7dc9ec03a5 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Alexander <a.burdun@gmail.com>
Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: wilfriedarma <wilfriedarma.collet@gmail.com>
Co-authored-by: zichichi <sollami@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translation: Servarr/Radarr
2024-01-24 08:59:10 +02:00
Servarr
33228335e3 Automated API Docs update 2024-01-24 08:58:09 +02:00
Mark McDowall
833340f8bd New: Add size to more history events
(cherry picked from commit 0d064181941fc6d149fc2f891661e059758d5428)

Closes #9672
2024-01-24 08:46:54 +02:00
Mark McDowall
0ecb1d0706 New: Add download client name to pending items waiting for a specific client
(cherry picked from commit 3cd4c67ba12cd5e8cc00d3df8929555fc0ad5918)

Closes #9676
2024-01-24 08:41:27 +02:00
Bogdan
25b77eb4a2 Update database migration version translation token
(cherry picked from commit 7d0d503a5e132cda3c03d6f7cd7b51c9c80740de)

Closes #9679
2024-01-24 08:30:52 +02:00
Stevie Robinson
b946173c05 Add Regular Expression Custom Format translation
(cherry picked from commit 9f50166fa62a71d0a23e2c2d331651792285dc0e)

Closes #9678
2024-01-24 08:28:10 +02:00
Bogdan
e5ccc32a37 Fixed: Movie status after using movie editor
Fixes #9681
2024-01-24 08:20:02 +02:00
Bogdan
3aeb52c3fd Fixed: Sorting by name in Manage Indexer and Download Client modals 2024-01-22 14:13:22 +02:00
Servarr
c717989034 Automated API Docs update 2024-01-21 16:24:34 +02:00
Bogdan
806b89abbe Fixed: Use movie file from state for status label in add search results 2024-01-21 16:17:46 +02:00
Bogdan
cc7104a814 Movie stats for Calendar 2024-01-21 16:17:46 +02:00
Bogdan
84c2d7f69d Revert "Add SizeOnDisk and HasFile to MovieResource"
This reverts commit 0ae8952b38.
2024-01-21 16:17:46 +02:00
bakerboy448
fcd187970c Improve Release Title Custom Format debugging
(cherry picked from commit ec40bc6eea1eb282cb804b8dd5461bf5ade332e9)

Closes #9653
2024-01-21 07:34:12 +02:00
Bogdan
34eb59dde4 Bump version to 5.3.3 2024-01-21 07:32:09 +02:00
Weblate
31b66c6673 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Blair Noctis <fqmxz5hyfba7ft85@neon.casa>
Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Julian Baquero <julian-baquero@upc.edu.co>
Co-authored-by: Koch Norbert <kochnorbert@icloud.com>
Co-authored-by: MaddionMax <kovacs.tamas@ius.hu>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: blabla <romcrack56@gmail.com>
Co-authored-by: brn <barantsenkul@gmail.com>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: horvi28 <horvi28@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2024-01-21 03:50:25 +02:00
245 changed files with 2740 additions and 1695 deletions

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '5.3.2'
majorVersion: '5.3.3'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'

View File

@@ -25,7 +25,7 @@ import toggleSelected from 'Utilities/Table/toggleSelected';
import QueueFilterModal from './QueueFilterModal';
import QueueOptionsConnector from './QueueOptionsConnector';
import QueueRowConnector from './QueueRowConnector';
import RemoveQueueItemsModal from './RemoveQueueItemsModal';
import RemoveQueueItemModal from './RemoveQueueItemModal';
class Queue extends Component {
@@ -307,9 +307,16 @@ class Queue extends Component {
}
</PageContentBody>
<RemoveQueueItemsModal
<RemoveQueueItemModal
isOpen={isConfirmRemoveModalOpen}
selectedCount={selectedCount}
canChangeCategory={isConfirmRemoveModalOpen && (
selectedIds.every((id) => {
const item = items.find((i) => i.id === id);
return !!(item && item.downloadClientHasPostImportCategory);
})
)}
canIgnore={isConfirmRemoveModalOpen && (
selectedIds.every((id) => {
const item = items.find((i) => i.id === id);
@@ -317,7 +324,7 @@ class Queue extends Component {
return !!(item && item.movieId);
})
)}
allPending={isConfirmRemoveModalOpen && (
pending={isConfirmRemoveModalOpen && (
selectedIds.every((id) => {
const item = items.find((i) => i.id === id);

View File

@@ -96,6 +96,7 @@ class QueueRow extends Component {
indexer,
outputPath,
downloadClient,
downloadClientHasPostImportCategory,
estimatedCompletionTime,
added,
timeleft,
@@ -373,6 +374,7 @@ class QueueRow extends Component {
<RemoveQueueItemModal
isOpen={isRemoveQueueItemModalOpen}
sourceTitle={title}
canChangeCategory={!!downloadClientHasPostImportCategory}
canIgnore={!!movie}
isPending={isPending}
onRemovePress={this.onRemoveQueueItemModalConfirmed}
@@ -402,6 +404,7 @@ QueueRow.propTypes = {
indexer: PropTypes.string,
outputPath: PropTypes.string,
downloadClient: PropTypes.string,
downloadClientHasPostImportCategory: PropTypes.bool,
estimatedCompletionTime: PropTypes.string,
added: PropTypes.string,
timeleft: PropTypes.string,

View File

@@ -1,171 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Button from 'Components/Link/Button';
import Modal from 'Components/Modal/Modal';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
class RemoveQueueItemModal extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
remove: true,
blocklist: false,
skipRedownload: false
};
}
//
// Control
resetState = function() {
this.setState({
remove: true,
blocklist: false,
skipRedownload: false
});
};
//
// Listeners
onRemoveChange = ({ value }) => {
this.setState({ remove: value });
};
onBlocklistChange = ({ value }) => {
this.setState({ blocklist: value });
};
onSkipRedownloadChange = ({ value }) => {
this.setState({ skipRedownload: value });
};
onRemoveConfirmed = () => {
const state = this.state;
this.resetState();
this.props.onRemovePress(state);
};
onModalClose = () => {
this.resetState();
this.props.onModalClose();
};
//
// Render
render() {
const {
isOpen,
sourceTitle,
canIgnore,
isPending
} = this.props;
const { remove, blocklist, skipRedownload } = this.state;
return (
<Modal
isOpen={isOpen}
size={sizes.MEDIUM}
onModalClose={this.onModalClose}
>
<ModalContent
onModalClose={this.onModalClose}
>
<ModalHeader>
{translate('RemoveQueueItem', { sourceTitle })}
</ModalHeader>
<ModalBody>
<div>
{translate('RemoveQueueItemConfirmation', { sourceTitle })}
</div>
{
isPending ?
null :
<FormGroup>
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="remove"
value={remove}
helpTextWarning={translate('RemoveFromDownloadClientHelpTextWarning')}
isDisabled={!canIgnore}
onChange={this.onRemoveChange}
/>
</FormGroup>
}
<FormGroup>
<FormLabel>{translate('BlocklistRelease')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="blocklist"
value={blocklist}
helpText={translate('BlocklistReleaseHelpText')}
onChange={this.onBlocklistChange}
/>
</FormGroup>
{
blocklist ?
<FormGroup>
<FormLabel>{translate('SkipRedownload')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="skipRedownload"
value={skipRedownload}
helpText={translate('SkipRedownloadHelpText')}
onChange={this.onSkipRedownloadChange}
/>
</FormGroup> :
null
}
</ModalBody>
<ModalFooter>
<Button onPress={this.onModalClose}>
{translate('Close')}
</Button>
<Button
kind={kinds.DANGER}
onPress={this.onRemoveConfirmed}
>
{translate('Remove')}
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
}
}
RemoveQueueItemModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
sourceTitle: PropTypes.string.isRequired,
canIgnore: PropTypes.bool.isRequired,
isPending: PropTypes.bool.isRequired,
onRemovePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default RemoveQueueItemModal;

View File

@@ -0,0 +1,230 @@
import React, { useCallback, useMemo, useState } from 'react';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Button from 'Components/Link/Button';
import Modal from 'Components/Modal/Modal';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './RemoveQueueItemModal.css';
interface RemovePressProps {
remove: boolean;
changeCategory: boolean;
blocklist: boolean;
skipRedownload: boolean;
}
interface RemoveQueueItemModalProps {
isOpen: boolean;
sourceTitle: string;
canChangeCategory: boolean;
canIgnore: boolean;
isPending: boolean;
selectedCount?: number;
onRemovePress(props: RemovePressProps): void;
onModalClose: () => void;
}
type RemovalMethod = 'removeFromClient' | 'changeCategory' | 'ignore';
type BlocklistMethod =
| 'doNotBlocklist'
| 'blocklistAndSearch'
| 'blocklistOnly';
function RemoveQueueItemModal(props: RemoveQueueItemModalProps) {
const {
isOpen,
sourceTitle,
canIgnore,
canChangeCategory,
isPending,
selectedCount,
onRemovePress,
onModalClose,
} = props;
const multipleSelected = selectedCount && selectedCount > 1;
const [removalMethod, setRemovalMethod] =
useState<RemovalMethod>('removeFromClient');
const [blocklistMethod, setBlocklistMethod] =
useState<BlocklistMethod>('doNotBlocklist');
const { title, message } = useMemo(() => {
if (!selectedCount) {
return {
title: translate('RemoveQueueItem', { sourceTitle }),
message: translate('RemoveQueueItemConfirmation', { sourceTitle }),
};
}
if (selectedCount === 1) {
return {
title: translate('RemoveSelectedItem'),
message: translate('RemoveSelectedItemQueueMessageText'),
};
}
return {
title: translate('RemoveSelectedItems'),
message: translate('RemoveSelectedItemsQueueMessageText', {
selectedCount,
}),
};
}, [sourceTitle, selectedCount]);
const removalMethodOptions = useMemo(() => {
return [
{
key: 'removeFromClient',
value: translate('RemoveFromDownloadClient'),
hint: multipleSelected
? translate('RemoveMultipleFromDownloadClientHint')
: translate('RemoveFromDownloadClientHint'),
},
{
key: 'changeCategory',
value: translate('ChangeCategory'),
isDisabled: !canChangeCategory,
hint: multipleSelected
? translate('ChangeCategoryMultipleHint')
: translate('ChangeCategoryHint'),
},
{
key: 'ignore',
value: multipleSelected
? translate('IgnoreDownloads')
: translate('IgnoreDownload'),
isDisabled: !canIgnore,
hint: multipleSelected
? translate('IgnoreDownloadsHint')
: translate('IgnoreDownloadHint'),
},
];
}, [canChangeCategory, canIgnore, multipleSelected]);
const blocklistMethodOptions = useMemo(() => {
return [
{
key: 'doNotBlocklist',
value: translate('DoNotBlocklist'),
hint: translate('DoNotBlocklistHint'),
},
{
key: 'blocklistAndSearch',
value: translate('BlocklistAndSearch'),
hint: multipleSelected
? translate('BlocklistAndSearchMultipleHint')
: translate('BlocklistAndSearchHint'),
},
{
key: 'blocklistOnly',
value: translate('BlocklistOnly'),
hint: multipleSelected
? translate('BlocklistMultipleOnlyHint')
: translate('BlocklistOnlyHint'),
},
];
}, [multipleSelected]);
const handleRemovalMethodChange = useCallback(
({ value }: { value: RemovalMethod }) => {
setRemovalMethod(value);
},
[setRemovalMethod]
);
const handleBlocklistMethodChange = useCallback(
({ value }: { value: BlocklistMethod }) => {
setBlocklistMethod(value);
},
[setBlocklistMethod]
);
const handleConfirmRemove = useCallback(() => {
onRemovePress({
remove: removalMethod === 'removeFromClient',
changeCategory: removalMethod === 'changeCategory',
blocklist: blocklistMethod !== 'doNotBlocklist',
skipRedownload: blocklistMethod === 'blocklistOnly',
});
setRemovalMethod('removeFromClient');
setBlocklistMethod('doNotBlocklist');
}, [
removalMethod,
blocklistMethod,
setRemovalMethod,
setBlocklistMethod,
onRemovePress,
]);
const handleModalClose = useCallback(() => {
setRemovalMethod('removeFromClient');
setBlocklistMethod('doNotBlocklist');
onModalClose();
}, [setRemovalMethod, setBlocklistMethod, onModalClose]);
return (
<Modal isOpen={isOpen} size={sizes.MEDIUM} onModalClose={handleModalClose}>
<ModalContent onModalClose={handleModalClose}>
<ModalHeader>{title}</ModalHeader>
<ModalBody>
<div className={styles.message}>{message}</div>
{isPending ? null : (
<FormGroup>
<FormLabel>{translate('RemoveQueueItemRemovalMethod')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="removalMethod"
value={removalMethod}
values={removalMethodOptions}
isDisabled={!canChangeCategory && !canIgnore}
helpTextWarning={translate(
'RemoveQueueItemRemovalMethodHelpTextWarning'
)}
onChange={handleRemovalMethodChange}
/>
</FormGroup>
)}
<FormGroup>
<FormLabel>
{multipleSelected
? translate('BlocklistReleases')
: translate('BlocklistRelease')}
</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="blocklistMethod"
value={blocklistMethod}
values={blocklistMethodOptions}
helpText={translate('BlocklistReleaseHelpText')}
onChange={handleBlocklistMethodChange}
/>
</FormGroup>
</ModalBody>
<ModalFooter>
<Button onPress={handleModalClose}>{translate('Close')}</Button>
<Button kind={kinds.DANGER} onPress={handleConfirmRemove}>
{translate('Remove')}
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
}
export default RemoveQueueItemModal;

View File

@@ -1,174 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Button from 'Components/Link/Button';
import Modal from 'Components/Modal/Modal';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './RemoveQueueItemsModal.css';
class RemoveQueueItemsModal extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
remove: true,
blocklist: false,
skipRedownload: false
};
}
//
// Control
resetState = function() {
this.setState({
remove: true,
blocklist: false,
skipRedownload: false
});
};
//
// Listeners
onRemoveChange = ({ value }) => {
this.setState({ remove: value });
};
onBlocklistChange = ({ value }) => {
this.setState({ blocklist: value });
};
onSkipRedownloadChange = ({ value }) => {
this.setState({ skipRedownload: value });
};
onRemoveConfirmed = () => {
const state = this.state;
this.resetState();
this.props.onRemovePress(state);
};
onModalClose = () => {
this.resetState();
this.props.onModalClose();
};
//
// Render
render() {
const {
isOpen,
selectedCount,
canIgnore,
allPending
} = this.props;
const { remove, blocklist, skipRedownload } = this.state;
return (
<Modal
isOpen={isOpen}
size={sizes.MEDIUM}
onModalClose={this.onModalClose}
>
<ModalContent
onModalClose={this.onModalClose}
>
<ModalHeader>
{selectedCount > 1 ? translate('RemoveSelectedItems') : translate('RemoveSelectedItem')}
</ModalHeader>
<ModalBody>
<div className={styles.message}>
{selectedCount > 1 ? translate('RemoveSelectedItemsQueueMessageText', { selectedCount }) : translate('RemoveSelectedItemQueueMessageText')}
</div>
{
allPending ?
null :
<FormGroup>
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="remove"
value={remove}
helpTextWarning={translate('RemoveHelpTextWarning')}
isDisabled={!canIgnore}
onChange={this.onRemoveChange}
/>
</FormGroup>
}
<FormGroup>
<FormLabel>
{selectedCount > 1 ? translate('BlocklistReleases') : translate('BlocklistRelease')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="blocklist"
value={blocklist}
helpText={translate('BlocklistReleaseHelpText')}
onChange={this.onBlocklistChange}
/>
</FormGroup>
{
blocklist ?
<FormGroup>
<FormLabel>{translate('SkipRedownload')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="skipRedownload"
value={skipRedownload}
helpText={translate('SkipRedownloadHelpText')}
onChange={this.onSkipRedownloadChange}
/>
</FormGroup> :
null
}
</ModalBody>
<ModalFooter>
<Button onPress={this.onModalClose}>
{translate('Close')}
</Button>
<Button
kind={kinds.DANGER}
onPress={this.onRemoveConfirmed}
>
{translate('Remove')}
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
}
}
RemoveQueueItemsModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
selectedCount: PropTypes.number.isRequired,
canIgnore: PropTypes.bool.isRequired,
allPending: PropTypes.bool.isRequired,
onRemovePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default RemoveQueueItemsModal;

View File

@@ -3,10 +3,13 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { clearAddMovie, lookupMovie } from 'Store/Actions/addMovieActions';
import { clearMovieFiles, fetchMovieFiles } from 'Store/Actions/movieFileActions';
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import { fetchImportExclusions } from 'Store/Actions/Settings/importExclusions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
import parseUrl from 'Utilities/String/parseUrl';
import AddNewMovie from './AddNewMovie';
@@ -35,7 +38,9 @@ const mapDispatchToProps = {
fetchRootFolders,
fetchImportExclusions,
fetchQueueDetails,
clearQueueDetails
clearQueueDetails,
fetchMovieFiles,
clearMovieFiles
};
class AddNewMovieConnector extends Component {
@@ -55,6 +60,20 @@ class AddNewMovieConnector extends Component {
this.props.fetchQueueDetails();
}
componentDidUpdate(prevProps) {
const {
items
} = this.props;
if (hasDifferentItems(prevProps.items, items)) {
const movieIds = selectUniqueIds(items, 'internalId');
if (movieIds.length) {
this.props.fetchMovieFiles({ movieId: movieIds });
}
}
}
componentWillUnmount() {
if (this._movieLookupTimeout) {
clearTimeout(this._movieLookupTimeout);
@@ -62,6 +81,7 @@ class AddNewMovieConnector extends Component {
this.props.clearAddMovie();
this.props.clearQueueDetails();
this.props.clearMovieFiles();
}
//
@@ -107,12 +127,15 @@ class AddNewMovieConnector extends Component {
AddNewMovieConnector.propTypes = {
term: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
lookupMovie: PropTypes.func.isRequired,
clearAddMovie: PropTypes.func.isRequired,
fetchRootFolders: PropTypes.func.isRequired,
fetchImportExclusions: PropTypes.func.isRequired,
fetchQueueDetails: PropTypes.func.isRequired,
clearQueueDetails: PropTypes.func.isRequired
clearQueueDetails: PropTypes.func.isRequired,
fetchMovieFiles: PropTypes.func.isRequired,
clearMovieFiles: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(AddNewMovieConnector);

View File

@@ -75,7 +75,6 @@ class AddNewMovieSearchResult extends Component {
colorImpairedMode,
id,
monitored,
hasFile,
isAvailable,
movieFile,
queueItem,
@@ -88,6 +87,8 @@ class AddNewMovieSearchResult extends Component {
isNewAddMovieModalOpen
} = this.state;
const hasMovieFile = !!movieFile;
const linkProps = isExistingMovie ? { to: `/movie/${titleSlug}` } : { onPress: this.onPress };
const posterWidth = 167;
const posterHeight = 250;
@@ -126,7 +127,7 @@ class AddNewMovieSearchResult extends Component {
movieId={existingMovieId}
movieFile={movieFile}
monitored={monitored}
hasFile={hasFile}
hasFile={hasMovieFile}
status={status}
width={posterWidth}
detailedProgressBar={true}
@@ -270,7 +271,7 @@ class AddNewMovieSearchResult extends Component {
{
isExistingMovie && isSmallScreen &&
<MovieStatusLabel
hasMovieFiles={hasFile}
hasMovieFiles={hasMovieFile}
monitored={monitored}
isAvailable={isAvailable}
queueItem={queueItem}
@@ -322,7 +323,6 @@ AddNewMovieSearchResult.propTypes = {
isSmallScreen: PropTypes.bool.isRequired,
id: PropTypes.number,
monitored: PropTypes.bool.isRequired,
hasFile: PropTypes.bool.isRequired,
isAvailable: PropTypes.bool.isRequired,
movieFile: PropTypes.object,
queueItem: PropTypes.object,

View File

@@ -11,10 +11,12 @@ function createMapStateToProps() {
createExclusionMovieSelector(),
createDimensionsSelector(),
(state) => state.queue.details.items,
(state) => state.movieFiles.items,
(state, { internalId }) => internalId,
(state) => state.settings.ui.item.movieRuntimeFormat,
(isExistingMovie, isExclusionMovie, dimensions, queueItems, internalId, movieRuntimeFormat) => {
(isExistingMovie, isExclusionMovie, dimensions, queueItems, movieFiles, internalId, movieRuntimeFormat) => {
const queueItem = queueItems.find((item) => internalId > 0 && item.movieId === internalId);
const movieFile = movieFiles.find((item) => internalId > 0 && item.movieId === internalId);
return {
existingMovieId: internalId,
@@ -22,6 +24,7 @@ function createMapStateToProps() {
isExclusionMovie,
isSmallScreen: dimensions.isSmallScreen,
queueItem,
movieFile,
movieRuntimeFormat
};
}

View File

@@ -33,9 +33,7 @@ class Calendar extends Component {
{
!isFetching && !!error &&
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadTheCalendar')}
</Alert>
<Alert kind={kinds.DANGER}>{translate('CalendarLoadError')}</Alert>
}
{

View File

@@ -104,7 +104,7 @@ class CalendarPage extends Component {
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
label={translate('iCalLink')}
label={translate('ICalLink')}
iconName={icons.CALENDAR}
onPress={this.onGetCalendarLinkPress}
/>
@@ -112,7 +112,7 @@ class CalendarPage extends Component {
<PageToolbarSeparator />
<PageToolbarButton
label={translate('RSSSync')}
label={translate('RssSync')}
iconName={icons.RSS}
isSpinning={isRssSyncExecuting}
onPress={onRssSyncPress}
@@ -180,7 +180,7 @@ class CalendarPage extends Component {
{
!movieError && movieIsPopulated && !hasMovie &&
<NoMovie />
<NoMovie totalItems={0} />
}
{

View File

@@ -126,7 +126,7 @@ class CalendarHeader extends Component {
isDisabled={view === calendarViews.AGENDA}
onPress={onTodayPress}
>
Today
{translate('Today')}
</Button>
</div>

View File

@@ -135,7 +135,7 @@ class CalendarOptionsModalContent extends Component {
type={inputTypes.CHECK}
name="showCutoffUnmetIcon"
value={showCutoffUnmetIcon}
helpText={translate('ShowCutoffUnmetIconHelpText')}
helpText={translate('IconForCutoffUnmetHelpText')}
onChange={this.onOptionInputChange}
/>
</FormGroup>
@@ -177,7 +177,7 @@ class CalendarOptionsModalContent extends Component {
values={weekColumnOptions}
value={calendarWeekColumnHeader}
onChange={this.onGlobalInputChange}
helpText={translate('SettingsWeekColumnHeaderHelpText')}
helpText={translate('WeekColumnHeaderHelpText')}
/>
</FormGroup>

View File

@@ -109,7 +109,7 @@ class CalendarLinkModalContent extends Component {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('RadarrCalendarFeed')}
{translate('CalendarFeed')}
</ModalHeader>
<ModalBody>
@@ -121,19 +121,19 @@ class CalendarLinkModalContent extends Component {
type={inputTypes.CHECK}
name="unmonitored"
value={unmonitored}
helpText={translate('UnmonitoredHelpText')}
helpText={translate('ICalIncludeUnmonitoredMoviesHelpText')}
onChange={this.onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('ShowAsAllDayEvents')}</FormLabel>
<FormLabel>{translate('ICalShowAsAllDayEvents')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="asAllDay"
value={asAllDay}
helpText={translate('AsAllDayHelpText')}
helpText={translate('ICalShowAsAllDayEventsHelpText')}
onChange={this.onInputChange}
/>
</FormGroup>
@@ -145,7 +145,7 @@ class CalendarLinkModalContent extends Component {
type={inputTypes.TAG}
name="tags"
value={tags}
helpText={translate('TagsHelpText')}
helpText={translate('ICalTagsMoviesHelpText')}
onChange={this.onInputChange}
/>
</FormGroup>
@@ -160,7 +160,7 @@ class CalendarLinkModalContent extends Component {
name="iCalHttpUrl"
value={iCalHttpUrl}
readOnly={true}
helpText={translate('ICalHttpUrlHelpText')}
helpText={translate('ICalFeedHelpText')}
buttons={[
<ClipboardButton
key="copy"

View File

@@ -267,6 +267,7 @@ FormInputGroup.propTypes = {
name: PropTypes.string.isRequired,
value: PropTypes.any,
values: PropTypes.arrayOf(PropTypes.any),
isDisabled: PropTypes.bool,
type: PropTypes.string.isRequired,
kind: PropTypes.oneOf(kinds.all),
min: PropTypes.number,

View File

@@ -1,8 +1,14 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Icon from 'Components/Icon';
import Label from 'Components/Label';
import Link from 'Components/Link/Link';
import MonitorToggleButton from 'Components/MonitorToggleButton';
import Popover from 'Components/Tooltip/Popover';
import { icons, kinds, sizes } from 'Helpers/Props';
import MovieHeadshot from 'Movie/MovieHeadshot';
import EditImportListModalConnector from 'Settings/ImportLists/ImportLists/EditImportListModalConnector';
import translate from 'Utilities/String/translate';
import styles from '../MovieCreditPoster.css';
class MovieCastPoster extends Component {
@@ -52,6 +58,7 @@ class MovieCastPoster extends Component {
render() {
const {
tmdbId,
personName,
character,
images,
@@ -83,15 +90,35 @@ class MovieCastPoster extends Component {
style={contentStyle}
>
<div className={styles.posterContainer}>
<div className={styles.controls}>
<div className={styles.toggleMonitoredContainer}>
<MonitorToggleButton
className={styles.action}
className={styles.monitorToggleButton}
monitored={monitored}
size={20}
onPress={importListId > 0 ? this.onEditImportListPress : this.onAddImportListPress}
/>
</div>
<Label className={styles.controls}>
<span className={styles.externalLinks}>
<Popover
anchor={<Icon name={icons.EXTERNAL_LINK} size={12} />}
title={translate('Links')}
body={
<Link to={`https://www.themoviedb.org/person/${tmdbId}`}>
<Label
className={styles.externalLinkLabel}
kind={kinds.INFO}
size={sizes.LARGE}
>
{translate('TMDb')}
</Label>
</Link>
}
/>
</span>
</Label>
<div
style={elementStyle}
>

View File

@@ -1,8 +1,14 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Icon from 'Components/Icon';
import Label from 'Components/Label';
import Link from 'Components/Link/Link';
import MonitorToggleButton from 'Components/MonitorToggleButton';
import Popover from 'Components/Tooltip/Popover';
import { icons, kinds, sizes } from 'Helpers/Props';
import MovieHeadshot from 'Movie/MovieHeadshot';
import EditImportListModalConnector from 'Settings/ImportLists/ImportLists/EditImportListModalConnector';
import translate from 'Utilities/String/translate';
import styles from '../MovieCreditPoster.css';
class MovieCrewPoster extends Component {
@@ -52,6 +58,7 @@ class MovieCrewPoster extends Component {
render() {
const {
tmdbId,
personName,
job,
images,
@@ -83,15 +90,35 @@ class MovieCrewPoster extends Component {
style={contentStyle}
>
<div className={styles.posterContainer}>
<div className={styles.controls}>
<div className={styles.toggleMonitoredContainer}>
<MonitorToggleButton
className={styles.action}
className={styles.monitorToggleButton}
monitored={monitored}
size={20}
onPress={importListId > 0 ? this.onEditImportListPress : this.onAddImportListPress}
/>
</div>
<Label className={styles.controls}>
<span className={styles.externalLinks}>
<Popover
anchor={<Icon name={icons.EXTERNAL_LINK} size={12} />}
title={translate('Links')}
body={
<Link to={`https://www.themoviedb.org/person/${tmdbId}`}>
<Label
className={styles.externalLinkLabel}
kind={kinds.INFO}
size={sizes.LARGE}
>
{translate('TMDb')}
</Label>
</Link>
}
/>
</span>
</Label>
<div
style={elementStyle}
>

View File

@@ -1,13 +1,17 @@
$hoverScale: 1.05;
.content {
border-radius: '5px';
transition: all 200ms ease-in;
&:hover {
z-index: 2;
box-shadow: 0 0 12px var(--black);
transition: all 200ms ease-in;
.controls {
opacity: 0.9;
transition: opacity 200ms linear 150ms;
}
}
}
@@ -44,13 +48,13 @@ $hoverScale: 1.05;
font-size: $smallFontSize;
}
.controls {
.toggleMonitoredContainer {
position: absolute;
top: 10px;
z-index: 3;
}
.action {
.monitorToggleButton {
composes: toggleButton from '~Components/MonitorToggleButton.css';
width: 25px;
@@ -61,8 +65,39 @@ $hoverScale: 1.05;
}
}
.controls {
position: absolute;
bottom: 10px;
left: 10px;
z-index: 3;
border-radius: 4px;
background-color: #707070;
color: var(--white);
font-size: $smallFontSize;
opacity: 0;
transition: opacity 0;
}
.action {
composes: button from '~Components/Link/IconButton.css';
&:hover {
color: var(--iconButtonHoverLightColor);
}
}
@media only screen and (max-width: $breakpointSmall) {
.container {
padding: 5px;
}
}
.externalLinks {
margin: 0 2px;
}
.externalLinkLabel {
composes: label from '~Components/Label.css';
cursor: pointer;
}

View File

@@ -5,10 +5,14 @@ interface CssExports {
'container': string;
'content': string;
'controls': string;
'externalLinkLabel': string;
'externalLinks': string;
'monitorToggleButton': string;
'overlayTitle': string;
'poster': string;
'posterContainer': string;
'title': string;
'toggleMonitoredContainer': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -45,7 +45,8 @@
width: 165px;
}
.releaseGroup {
.releaseGroup,
.dateAdded {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 120px;

View File

@@ -6,6 +6,7 @@ interface CssExports {
'audio': string;
'audioLanguages': string;
'customFormatScore': string;
'dateAdded': string;
'download': string;
'formats': string;
'language': string;

View File

@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import Tooltip from 'Components/Tooltip/Tooltip';
@@ -82,6 +83,7 @@ class MovieFileEditorRow extends Component {
customFormats,
customFormatScore,
languages,
dateAdded,
columns
} = this.props;
@@ -287,6 +289,16 @@ class MovieFileEditorRow extends Component {
);
}
if (name === 'dateAdded') {
return (
<RelativeDateCellConnector
key={name}
className={styles.dateAdded}
date={dateAdded}
/>
);
}
if (name === 'actions') {
return (
<TableRowCell key={name} className={styles.actions}>
@@ -354,6 +366,7 @@ MovieFileEditorRow.propTypes = {
qualityCutoffNotMet: PropTypes.bool.isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
mediaInfo: PropTypes.object,
dateAdded: PropTypes.string,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onDeletePress: PropTypes.func.isRequired
};

View File

@@ -49,7 +49,7 @@ function EditSpecificationModalContent(props) {
{...otherProps}
>
{
fields && fields.some((x) => x.label === 'Regular Expression') &&
fields && fields.some((x) => x.label === translate('CustomFormatsSpecificationRegularExpression')) &&
<Alert kind={kinds.INFO}>
<div>
<div dangerouslySetInnerHTML={{ __html: translate('ThisConditionMatchesUsingRegularExpressions', ['<code>\\^$.|?*+()[{</code>', '<code>\\</code>']) }} />

View File

@@ -94,7 +94,12 @@ export default {
items: [],
pendingChanges: {},
sortKey: 'name',
sortDirection: sortDirections.DESCENDING
sortDirection: sortDirections.ASCENDING,
sortPredicates: {
name: function(item) {
return item.name.toLowerCase();
}
}
},
//

View File

@@ -99,7 +99,12 @@ export default {
items: [],
pendingChanges: {},
sortKey: 'name',
sortDirection: sortDirections.DESCENDING
sortDirection: sortDirections.ASCENDING,
sortPredicates: {
name: function(item) {
return item.name.toLowerCase();
}
}
},
//

View File

@@ -98,6 +98,11 @@ export const defaultState = {
}),
isVisible: true
},
{
name: 'dateAdded',
label: () => translate('Added'),
isVisible: false
},
{
name: 'actions',
columnLabel: () => translate('Actions'),

View File

@@ -419,13 +419,14 @@ export const actionHandlers = handleThunks({
id,
remove,
blocklist,
skipRedownload
skipRedownload,
changeCategory
} = 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}&changeCategory=${changeCategory}`,
method: 'DELETE'
}).request;
@@ -443,7 +444,8 @@ export const actionHandlers = handleThunks({
ids,
remove,
blocklist,
skipRedownload
skipRedownload,
changeCategory
} = payload;
dispatch(batchActions([
@@ -459,7 +461,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}&changeCategory=${changeCategory}`,
method: 'DELETE',
dataType: 'json',
contentType: 'application/json',

View File

@@ -22,9 +22,9 @@ class About extends Component {
isNetCore,
isDocker,
runtimeVersion,
migrationVersion,
databaseVersion,
databaseType,
migrationVersion,
appData,
startupPath,
mode,
@@ -73,13 +73,13 @@ class About extends Component {
}
<DescriptionListItem
title={translate('DBMigration')}
data={migrationVersion}
title={translate('Database')}
data={`${titleCase(databaseType)} ${databaseVersion}`}
/>
<DescriptionListItem
title={translate('Database')}
data={`${titleCase(databaseType)} ${databaseVersion}`}
title={translate('DatabaseMigration')}
data={migrationVersion}
/>
<DescriptionListItem
@@ -121,9 +121,9 @@ About.propTypes = {
isNetCore: PropTypes.bool.isRequired,
runtimeVersion: PropTypes.string.isRequired,
isDocker: PropTypes.bool.isRequired,
migrationVersion: PropTypes.number.isRequired,
databaseType: PropTypes.string.isRequired,
databaseVersion: PropTypes.string.isRequired,
migrationVersion: PropTypes.number.isRequired,
appData: PropTypes.string.isRequired,
startupPath: PropTypes.string.isRequired,
mode: PropTypes.string.isRequired,

View File

@@ -130,7 +130,7 @@ namespace NzbDrone.Common.Disk
try
{
var testPath = Path.Combine(path, "radarr_write_test.txt");
var testContent = string.Format("This file was created to verify if '{0}' is writable. It should've been automatically deleted. Feel free to delete it.", path);
var testContent = $"This file was created to verify if '{path}' is writable. It should've been automatically deleted. Feel free to delete it.";
WriteAllText(testPath, testContent);
File.Delete(testPath);
return true;

View File

@@ -0,0 +1,166 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.Notifications.Email;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Migration
{
[TestFixture]
public class email_encryptionFixture : MigrationTest<email_encryption>
{
[Test]
public void should_convert_do_not_require_encryption_to_auto()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Notifications").Row(new
{
OnGrab = true,
OnDownload = true,
OnUpgrade = true,
OnHealthIssue = true,
IncludeHealthWarnings = true,
OnRename = true,
OnMovieDelete = false,
Name = "Mail Radarr",
Implementation = "Email",
Tags = "[]",
Settings = new EmailSettings234
{
Server = "smtp.gmail.com",
Port = 563,
To = new List<string> { "dont@email.me" },
RequireEncryption = false
}.ToJson(),
ConfigContract = "EmailSettings"
});
});
var items = db.Query<NotificationDefinition235>("SELECT * FROM \"Notifications\"");
items.Should().HaveCount(1);
items.First().Implementation.Should().Be("Email");
items.First().ConfigContract.Should().Be("EmailSettings");
items.First().Settings.UseEncryption.Should().Be((int)EmailEncryptionType.Preferred);
}
[Test]
public void should_convert_require_encryption_to_always()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Notifications").Row(new
{
OnGrab = true,
OnDownload = true,
OnUpgrade = true,
OnHealthIssue = true,
IncludeHealthWarnings = true,
OnRename = true,
OnMovieDelete = false,
Name = "Mail Radarr",
Implementation = "Email",
Tags = "[]",
Settings = new EmailSettings234
{
Server = "smtp.gmail.com",
Port = 563,
To = new List<string> { "dont@email.me" },
RequireEncryption = true
}.ToJson(),
ConfigContract = "EmailSettings"
});
});
var items = db.Query<NotificationDefinition235>("SELECT * FROM \"Notifications\"");
items.Should().HaveCount(1);
items.First().Implementation.Should().Be("Email");
items.First().ConfigContract.Should().Be("EmailSettings");
items.First().Settings.UseEncryption.Should().Be((int)EmailEncryptionType.Always);
}
[Test]
public void should_use_defaults_when_settings_are_empty()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Notifications").Row(new
{
OnGrab = true,
OnDownload = true,
OnUpgrade = true,
OnHealthIssue = true,
IncludeHealthWarnings = true,
OnRename = true,
OnMovieDelete = false,
Name = "Mail Radarr",
Implementation = "Email",
Tags = "[]",
Settings = new { }.ToJson(),
ConfigContract = "EmailSettings"
});
});
var items = db.Query<NotificationDefinition235>("SELECT * FROM \"Notifications\"");
items.Should().HaveCount(1);
items.First().Implementation.Should().Be("Email");
items.First().ConfigContract.Should().Be("EmailSettings");
items.First().Settings.UseEncryption.Should().Be((int)EmailEncryptionType.Preferred);
}
}
public class NotificationDefinition235
{
public int Id { get; set; }
public string Implementation { get; set; }
public string ConfigContract { get; set; }
public EmailSettings235 Settings { get; set; }
public string Name { get; set; }
public bool OnGrab { get; set; }
public bool OnDownload { get; set; }
public bool OnUpgrade { get; set; }
public bool OnRename { get; set; }
public bool OnMovieDelete { get; set; }
public bool OnMovieFileDelete { get; set; }
public bool OnMovieFileDeleteForUpgrade { get; set; }
public bool OnHealthIssue { get; set; }
public bool OnApplicationUpdate { get; set; }
public bool OnManualInteractionRequired { get; set; }
public bool OnMovieAdded { get; set; }
public bool OnHealthRestored { get; set; }
public bool IncludeHealthWarnings { get; set; }
public List<int> Tags { get; set; }
}
public class EmailSettings234
{
public string Server { get; set; }
public int Port { get; set; }
public bool RequireEncryption { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string From { get; set; }
public IEnumerable<string> To { get; set; }
public IEnumerable<string> Cc { get; set; }
public IEnumerable<string> Bcc { get; set; }
}
public class EmailSettings235
{
public string Server { get; set; }
public int Port { get; set; }
public int UseEncryption { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string From { get; set; }
public IEnumerable<string> To { get; set; }
public IEnumerable<string> Cc { get; set; }
public IEnumerable<string> Bcc { get; set; }
}
}

View File

@@ -320,7 +320,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.HadoukenTests
var result = Subject.Test();
result.Errors.First().ErrorMessage.Should().Be("Old Hadouken client with unsupported API, need 5.1 or higher");
result.Errors.Count.Should().Be(1);
}
}
}

View File

@@ -454,6 +454,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
[TestCase("0")]
[TestCase("15d")]
[TestCase("")]
[TestCase(null)]
public void should_set_history_removes_completed_downloads_false(string historyRetention)
{
_config.Misc.history_retention = historyRetention;

View File

@@ -176,7 +176,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
public void should_return_ok_on_movie_imported_event()
{
GivenFolderExists(_downloadRootPath);
var importEvent = new MovieFileImportedEvent(new LocalMovie(), new MovieFile(), new List<MovieFile>(), true, new DownloadClientItem());
var importEvent = new MovieFileImportedEvent(new LocalMovie(), new MovieFile(), new List<DeletedMovieFile>(), true, new DownloadClientItem());
Subject.Check(importEvent).ShouldBeOk();
}

View File

@@ -91,7 +91,7 @@ namespace NzbDrone.Core.Test.HistoryTests
DownloadId = "abcd"
};
Subject.Handle(new MovieFileImportedEvent(localMovie, movieFile, new List<MovieFile>(), true, downloadClientItem));
Subject.Handle(new MovieFileImportedEvent(localMovie, movieFile, new List<DeletedMovieFile>(), true, downloadClientItem));
Mocker.GetMock<IHistoryRepository>()
.Verify(v => v.Insert(It.Is<MovieHistory>(h => h.SourceTitle == Path.GetFileNameWithoutExtension(localMovie.Path))));

View File

@@ -25,6 +25,7 @@ namespace NzbDrone.Core.Test.NotificationTests.EmailTests
_emailSettings = Builder<EmailSettings>.CreateNew()
.With(s => s.Server = "someserver")
.With(s => s.Port = 567)
.With(s => s.UseEncryption = (int)EmailEncryptionType.Always)
.With(s => s.From = "radarr@radarr.video")
.With(s => s.To = new string[] { "radarr@radarr.video" })
.Build();

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles;
@@ -33,16 +33,16 @@ namespace NzbDrone.Core.Test.NotificationTests
RelativePath = "moviefile1.mkv"
},
OldMovieFiles = new List<MovieFile>
OldMovieFiles = new List<DeletedMovieFile>
{
new MovieFile
new DeletedMovieFile(new MovieFile
{
RelativePath = "oldmoviefile1.mkv"
},
new MovieFile
}, null),
new DeletedMovieFile(new MovieFile
{
RelativePath = "oldmoviefile2.mkv"
}
}, null)
}
};

View File

@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc
_downloadMessage = Builder<DownloadMessage>.CreateNew()
.With(d => d.Movie = movie)
.With(d => d.MovieFile = movieFile)
.With(d => d.OldMovieFiles = new List<MovieFile>())
.With(d => d.OldMovieFiles = new List<DeletedMovieFile>())
.Build();
Subject.Definition = new NotificationDefinition();
@@ -40,9 +40,12 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc
private void GivenOldFiles()
{
_downloadMessage.OldMovieFiles = Builder<MovieFile>.CreateListOfSize(1)
.Build()
.ToList();
_downloadMessage.OldMovieFiles = Builder<DeletedMovieFile>
.CreateListOfSize(1)
.All()
.WithFactory(() => new DeletedMovieFile(Builder<MovieFile>.CreateNew().Build(), null))
.Build()
.ToList();
Subject.Definition.Settings = new XbmcSettings
{

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.History;
@@ -25,10 +26,12 @@ namespace NzbDrone.Core.CustomFormats
public class CustomFormatCalculationService : ICustomFormatCalculationService
{
private readonly ICustomFormatService _formatService;
private readonly Logger _logger;
public CustomFormatCalculationService(ICustomFormatService formatService)
public CustomFormatCalculationService(ICustomFormatService formatService, Logger logger)
{
_formatService = formatService;
_logger = logger;
}
public List<CustomFormat> ParseCustomFormat(RemoteMovie remoteMovie, long size)
@@ -165,20 +168,23 @@ namespace NzbDrone.Core.CustomFormats
return matches.OrderBy(x => x.Name).ToList();
}
private static List<CustomFormat> ParseCustomFormat(MovieFile movieFile, Movie movie, List<CustomFormat> allCustomFormats)
private List<CustomFormat> ParseCustomFormat(MovieFile movieFile, Movie movie, List<CustomFormat> allCustomFormats)
{
var releaseTitle = string.Empty;
if (movieFile.SceneName.IsNotNullOrWhiteSpace())
{
_logger.Trace("Using scene name for release title: {0}", movieFile.SceneName);
releaseTitle = movieFile.SceneName;
}
else if (movieFile.OriginalFilePath.IsNotNullOrWhiteSpace())
{
_logger.Trace("Using original file path for release title: {0}", Path.GetFileName(movieFile.OriginalFilePath));
releaseTitle = Path.GetFileName(movieFile.OriginalFilePath);
}
else if (movieFile.RelativePath.IsNotNullOrWhiteSpace())
{
_logger.Trace("Using relative path for release title: {0}", Path.GetFileName(movieFile.RelativePath));
releaseTitle = Path.GetFileName(movieFile.RelativePath);
}

View File

@@ -21,7 +21,7 @@ namespace NzbDrone.Core.CustomFormats
protected Regex _regex;
protected string _raw;
[FieldDefinition(1, Label = "Regular Expression", HelpText = "Custom Format RegEx is Case Insensitive")]
[FieldDefinition(1, Label = "CustomFormatsSpecificationRegularExpression", HelpText = "CustomFormatsSpecificationRegularExpressionHelpText")]
public string Value
{
get => _raw;

View File

@@ -0,0 +1,50 @@
using System.Collections.Generic;
using System.Data;
using Dapper;
using FluentMigrator;
using Newtonsoft.Json.Linq;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(235)]
public class email_encryption : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Execute.WithConnection(ChangeEncryption);
}
private void ChangeEncryption(IDbConnection conn, IDbTransaction tran)
{
var updated = new List<object>();
using (var getEmailCmd = conn.CreateCommand())
{
getEmailCmd.Transaction = tran;
getEmailCmd.CommandText = "SELECT \"Id\", \"Settings\" FROM \"Notifications\" WHERE \"Implementation\" = 'Email'";
using (var reader = getEmailCmd.ExecuteReader())
{
while (reader.Read())
{
var id = reader.GetInt32(0);
var settings = Json.Deserialize<JObject>(reader.GetString(1));
settings["useEncryption"] = settings.Value<bool?>("requireEncryption") ?? false ? 1 : 0;
settings["requireEncryption"] = null;
updated.Add(new
{
Settings = settings.ToJson(),
Id = id
});
}
}
}
var updateSql = "UPDATE \"Notifications\" SET \"Settings\" = @Settings WHERE \"Id\" = @Id";
conn.Execute(updateSql, updated, transaction: tran);
}
}
}

View File

@@ -9,6 +9,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@@ -28,14 +29,15 @@ namespace NzbDrone.Core.Download.Clients.Aria2
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
{
_proxy = proxy;
}
protected override string AddFromMagnetLink(RemoteMovie remoteEpisode, string hash, string magnetLink)
protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
{
var gid = _proxy.AddMagnet(Settings, magnetLink);
@@ -54,7 +56,7 @@ namespace NzbDrone.Core.Download.Clients.Aria2
return hash;
}
protected override string AddFromTorrentFile(RemoteMovie remoteEpisode, string hash, string filename, byte[] fileContent)
protected override string AddFromTorrentFile(RemoteMovie remoteMovie, string hash, string filename, byte[] fileContent)
{
var gid = _proxy.AddTorrent(Settings, fileContent);
@@ -79,6 +81,7 @@ namespace NzbDrone.Core.Download.Clients.Aria2
{
var firstFile = torrent.Files?.FirstOrDefault();
// skip metadata download
if (firstFile?.Path?.Contains("[METADATA]") == true)
{
continue;
@@ -131,7 +134,7 @@ namespace NzbDrone.Core.Download.Clients.Aria2
CanMoveFiles = false,
CanBeRemoved = torrent.Status == "complete",
Category = null,
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this),
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this, false),
DownloadId = torrent.InfoHash?.ToUpper(),
IsEncrypted = false,
Message = torrent.ErrorMessage,
@@ -234,14 +237,19 @@ namespace NzbDrone.Core.Download.Clients.Aria2
if (new Version(version) < new Version("1.34.0"))
{
return new ValidationFailure(string.Empty, "Aria2 version should be at least 1.34.0. Version reported is {0}", version);
return new ValidationFailure(string.Empty,
_localizationService.GetLocalizedString("DownloadClientValidationErrorVersion",
new Dictionary<string, object>
{
{ "clientName", "Aria2" }, { "requiredVersion", "1.34.0" }, { "reportedVersion", version }
}));
}
}
catch (Exception ex)
{
_logger.Error(ex, "Failed to test Aria2");
return new NzbDroneValidationFailure("Host", "Unable to connect to Aria2")
return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", "Aria2" } }))
{
DetailedDescription = ex.Message
};

View File

@@ -32,13 +32,13 @@ namespace NzbDrone.Core.Download.Clients.Aria2
[FieldDefinition(1, Label = "Port", Type = FieldType.Number)]
public int Port { get; set; }
[FieldDefinition(2, Label = "XML RPC Path", Type = FieldType.Textbox)]
[FieldDefinition(2, Label = "XmlRpcPath", Type = FieldType.Textbox)]
public string RpcPath { get; set; }
[FieldDefinition(3, Label = "Use SSL", Type = FieldType.Checkbox)]
[FieldDefinition(3, Label = "UseSsl", Type = FieldType.Checkbox)]
public bool UseSsl { get; set; }
[FieldDefinition(4, Label = "Secret token", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
[FieldDefinition(4, Label = "SecretToken", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string SecretToken { get; set; }
[FieldDefinition(5, Label = "Directory", Type = FieldType.Textbox, HelpText = "DownloadClientAriaSettingsDirectoryHelpText")]

View File

@@ -9,6 +9,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
@@ -30,9 +31,10 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
{
_scanWatchFolder = scanWatchFolder;
@@ -81,7 +83,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
return null;
}
public override string Name => "Torrent Blackhole";
public override string Name => _localizationService.GetLocalizedString("TorrentBlackhole");
public override IEnumerable<DownloadClientItem> GetItems()
{
@@ -89,7 +91,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
{
yield return new DownloadClientItem
{
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this),
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this, false),
DownloadId = Definition.Name + "_" + item.DownloadId,
Category = "radarr",
Title = item.Title,

View File

@@ -1,4 +1,4 @@
using System.ComponentModel;
using System.ComponentModel;
using FluentValidation;
using Newtonsoft.Json;
using NzbDrone.Core.Annotations;
@@ -28,23 +28,24 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
private static readonly TorrentBlackholeSettingsValidator Validator = new TorrentBlackholeSettingsValidator();
[FieldDefinition(0, Label = "Torrent Folder", Type = FieldType.Path, HelpText = "Folder in which Radarr will store the .torrent file")]
[FieldDefinition(0, Label = "TorrentBlackholeTorrentFolder", Type = FieldType.Path, HelpText = "BlackholeFolderHelpText")]
[FieldToken(TokenField.HelpText, "TorrentBlackholeTorrentFolder", "extension", ".torrent")]
public string TorrentFolder { get; set; }
[FieldDefinition(1, Label = "Watch Folder", Type = FieldType.Path, HelpText = "Folder from which Radarr should import completed downloads")]
[FieldDefinition(1, Label = "BlackholeWatchFolder", Type = FieldType.Path, HelpText = "BlackholeWatchFolderHelpText")]
public string WatchFolder { get; set; }
[DefaultValue(false)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
[FieldDefinition(2, Label = "Save Magnet Files", Type = FieldType.Checkbox, HelpText = "Save a .magnet file with the magnet link if no .torrent file is available (only useful if the download client supports .magnet files)")]
[FieldDefinition(2, Label = "TorrentBlackholeSaveMagnetFiles", Type = FieldType.Checkbox, HelpText = "TorrentBlackholeSaveMagnetFilesHelpText")]
public bool SaveMagnetFiles { get; set; }
[FieldDefinition(3, Label = "Save Magnet Files", Type = FieldType.Textbox, HelpText = "Extension to use for magnet links, defaults to '.magnet'")]
[FieldDefinition(3, Label = "TorrentBlackholeSaveMagnetFilesExtension", Type = FieldType.Textbox, HelpText = "TorrentBlackholeSaveMagnetFilesExtensionHelpText")]
public string MagnetFileExtension { get; set; }
[DefaultValue(false)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
[FieldDefinition(4, Label = "Read Only", Type = FieldType.Checkbox, HelpText = "Instead of moving files this will instruct Radarr to Copy or Hardlink (depending on settings/system configuration)")]
[FieldDefinition(4, Label = "TorrentBlackholeSaveMagnetFilesReadOnly", Type = FieldType.Checkbox, HelpText = "TorrentBlackholeSaveMagnetFilesReadOnlyHelpText")]
public bool ReadOnly { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -7,6 +7,7 @@ using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@@ -25,8 +26,9 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IValidateNzbs nzbValidationService,
Logger logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger)
Logger logger,
ILocalizationService localizationService)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger, localizationService)
{
_scanWatchFolder = scanWatchFolder;
@@ -51,7 +53,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
return null;
}
public override string Name => "Usenet Blackhole";
public override string Name => _localizationService.GetLocalizedString("UsenetBlackhole");
public override IEnumerable<DownloadClientItem> GetItems()
{
@@ -59,7 +61,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
{
yield return new DownloadClientItem
{
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this),
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this, false),
DownloadId = Definition.Name + "_" + item.DownloadId,
Category = "Radarr",
Title = item.Title,

View File

@@ -1,4 +1,4 @@
using FluentValidation;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
@@ -19,10 +19,11 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
{
private static readonly UsenetBlackholeSettingsValidator Validator = new UsenetBlackholeSettingsValidator();
[FieldDefinition(0, Label = "Nzb Folder", Type = FieldType.Path, HelpText = "Folder in which Radarr will store the .nzb file")]
[FieldDefinition(0, Label = "UsenetBlackholeNzbFolder", Type = FieldType.Path, HelpText = "BlackholeFolderHelpText")]
[FieldToken(TokenField.HelpText, "UsenetBlackholeNzbFolder", "extension", ".nzb")]
public string NzbFolder { get; set; }
[FieldDefinition(1, Label = "Watch Folder", Type = FieldType.Path, HelpText = "Folder from which Radarr should import completed downloads")]
[FieldDefinition(1, Label = "BlackholeWatchFolder", Type = FieldType.Path, HelpText = "BlackholeWatchFolderHelpText")]
public string WatchFolder { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -1,4 +1,4 @@
using System;
using System;
using NzbDrone.Common.Disk;
namespace NzbDrone.Core.Download.Clients.Blackhole

View File

@@ -9,6 +9,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@@ -26,9 +27,10 @@ namespace NzbDrone.Core.Download.Clients.Deluge
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
{
_proxy = proxy;
}
@@ -135,7 +137,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
item.Title = torrent.Name;
item.Category = Settings.MovieCategory;
item.DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this);
item.DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this, Settings.MovieImportedCategory.IsNotNullOrWhiteSpace());
var outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(torrent.DownloadPath));
item.OutputPath = outputPath + torrent.Name;
@@ -157,7 +159,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
if (torrent.State == DelugeTorrentStatus.Error)
{
item.Status = DownloadItemStatus.Warning;
item.Message = "Deluge is reporting an error";
item.Message = _localizationService.GetLocalizedString("DownloadClientDelugeTorrentStateError");
}
else if (torrent.IsFinished && torrent.State != DelugeTorrentStatus.Checking)
{
@@ -250,7 +252,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
{
_logger.Error(ex, ex.Message);
return new NzbDroneValidationFailure("Password", "Authentication failed");
return new NzbDroneValidationFailure("Password", _localizationService.GetLocalizedString("DownloadClientValidationAuthenticationFailure"));
}
catch (WebException ex)
{
@@ -258,29 +260,29 @@ namespace NzbDrone.Core.Download.Clients.Deluge
switch (ex.Status)
{
case WebExceptionStatus.ConnectFailure:
return new NzbDroneValidationFailure("Host", "Unable to connect")
return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } }))
{
DetailedDescription = "Please verify the hostname and port."
DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnectDetail")
};
case WebExceptionStatus.ConnectionClosed:
return new NzbDroneValidationFailure("UseSsl", "Verify SSL settings")
return new NzbDroneValidationFailure("UseSsl", _localizationService.GetLocalizedString("DownloadClientValidationVerifySsl"))
{
DetailedDescription = "Please verify your SSL configuration on both Deluge and Radarr."
DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationVerifySslDetail", new Dictionary<string, object> { { "clientName", Name } })
};
case WebExceptionStatus.SecureChannelFailure:
return new NzbDroneValidationFailure("UseSsl", "Unable to connect through SSL")
return new NzbDroneValidationFailure("UseSsl", _localizationService.GetLocalizedString("DownloadClientValidationSslConnectFailure"))
{
DetailedDescription = "Radarr is unable to connect to Deluge using SSL. This problem could be computer related. Please try to configure both Radarr and Deluge to not use SSL."
DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationSslConnectFailureDetail", new Dictionary<string, object> { { "clientName", Name } })
};
default:
return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message);
return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationUnknownException", new Dictionary<string, object> { { "exception", ex.Message } }));
}
}
catch (Exception ex)
{
_logger.Error(ex, "Failed to test connection");
return new NzbDroneValidationFailure("Host", "Unable to connect to Deluge")
return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } }))
{
DetailedDescription = ex.Message
};
@@ -300,9 +302,9 @@ namespace NzbDrone.Core.Download.Clients.Deluge
if (!enabledPlugins.Contains("Label"))
{
return new NzbDroneValidationFailure("MovieCategory", "Label plugin not activated")
return new NzbDroneValidationFailure("MovieCategory", _localizationService.GetLocalizedString("DownloadClientDelugeValidationLabelPluginInactive"))
{
DetailedDescription = "You must have the Label plugin enabled in Deluge to use categories."
DetailedDescription = _localizationService.GetLocalizedString("DownloadClientDelugeValidationLabelPluginInactiveDetail", new Dictionary<string, object> { { "clientName", Name } })
};
}
@@ -315,9 +317,9 @@ namespace NzbDrone.Core.Download.Clients.Deluge
if (!labels.Contains(Settings.MovieCategory))
{
return new NzbDroneValidationFailure("MovieCategory", "Configuration of label failed")
return new NzbDroneValidationFailure("MovieCategory", _localizationService.GetLocalizedString("DownloadClientDelugeValidationLabelPluginFailure"))
{
DetailedDescription = "Radarr was unable to add the label to Deluge."
DetailedDescription = _localizationService.GetLocalizedString("DownloadClientDelugeValidationLabelPluginFailureDetail", new Dictionary<string, object> { { "clientName", Name } })
};
}
}
@@ -329,9 +331,9 @@ namespace NzbDrone.Core.Download.Clients.Deluge
if (!labels.Contains(Settings.MovieImportedCategory))
{
return new NzbDroneValidationFailure("MovieImportedCategory", "Configuration of label failed")
return new NzbDroneValidationFailure("MovieImportedCategory", _localizationService.GetLocalizedString("DownloadClientDelugeValidationLabelPluginFailure"))
{
DetailedDescription = "Radarr was unable to add the label to Deluge."
DetailedDescription = _localizationService.GetLocalizedString("DownloadClientDelugeValidationLabelPluginFailureDetail", new Dictionary<string, object> { { "clientName", Name } })
};
}
}
@@ -348,7 +350,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
catch (Exception ex)
{
_logger.Error(ex, "Unable to get torrents");
return new NzbDroneValidationFailure(string.Empty, "Failed to get the list of torrents: " + ex.Message);
return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationTestTorrents", new Dictionary<string, object> { { "exceptionMessage", ex.Message } }));
}
return null;

View File

@@ -1,4 +1,4 @@
namespace NzbDrone.Core.Download.Clients.Deluge
namespace NzbDrone.Core.Download.Clients.Deluge
{
public class DelugeError
{

View File

@@ -1,4 +1,4 @@
namespace NzbDrone.Core.Download.Clients.Deluge
namespace NzbDrone.Core.Download.Clients.Deluge
{
public class DelugeException : DownloadClientException
{

View File

@@ -1,4 +1,4 @@
namespace NzbDrone.Core.Download.Clients.Deluge
namespace NzbDrone.Core.Download.Clients.Deluge
{
public enum DelugePriority
{

View File

@@ -35,28 +35,30 @@ namespace NzbDrone.Core.Download.Clients.Deluge
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Deluge")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
[FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Deluge")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the deluge json url, see http://[host]:[port]/[urlBase]/json")]
[FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientDelugeSettingsUrlBaseHelpText")]
[FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/json")]
public string UrlBase { get; set; }
[FieldDefinition(4, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated non-Radarr downloads. Using a category is optional, but strongly recommended.")]
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategoryHelpText")]
public string MovieCategory { get; set; }
[FieldDefinition(6, Label = "Post-Import Category", Type = FieldType.Textbox, Advanced = true, HelpText = "Category for Radarr to set after it has imported the download. Radarr will not remove the torrent if seeding has finished. Leave blank to keep same category.")]
[FieldDefinition(6, Label = "PostImportCategory", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsPostImportCategoryHelpText")]
public string MovieImportedCategory { get; set; }
[FieldDefinition(7, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing movies that aired within the last 14 days")]
[FieldDefinition(7, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "DownloadClientSettingsRecentPriorityMovieHelpText")]
public int RecentMoviePriority { get; set; }
[FieldDefinition(8, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing movies that aired over 14 days ago")]
[FieldDefinition(8, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "DownloadClientSettingsOlderPriorityMovieHelpText")]
public int OlderMoviePriority { get; set; }
[FieldDefinition(9, Label = "Add Paused", Type = FieldType.Checkbox)]
[FieldDefinition(9, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox)]
public bool AddPaused { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -1,4 +1,4 @@
using Newtonsoft.Json;
using Newtonsoft.Json;
namespace NzbDrone.Core.Download.Clients.Deluge
{

View File

@@ -1,4 +1,4 @@
namespace NzbDrone.Core.Download.Clients.Deluge
namespace NzbDrone.Core.Download.Clients.Deluge
{
internal class DelugeTorrentStatus
{

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
namespace NzbDrone.Core.Download.Clients.Deluge
{

View File

@@ -1,4 +1,4 @@
using System;
using System;
namespace NzbDrone.Core.Download.Clients
{

View File

@@ -1,4 +1,4 @@
using System;
using System;
using NzbDrone.Common.Exceptions;
namespace NzbDrone.Core.Download.Clients

View File

@@ -1,4 +1,4 @@
using System;
using System;
namespace NzbDrone.Core.Download.Clients
{

View File

@@ -1,4 +1,4 @@
namespace NzbDrone.Core.Download.Clients.DownloadStation
namespace NzbDrone.Core.Download.Clients.DownloadStation
{
public class DiskStationApiInfo
{

View File

@@ -36,7 +36,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Download Station")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
[FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Download Station")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
@@ -45,10 +46,10 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
[FieldDefinition(4, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated non-Radarr downloads. Using a category is optional, but strongly recommended. Creates a [category] subdirectory in the output directory.")]
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategorySubFolderHelpText")]
public string TvCategory { get; set; }
[FieldDefinition(6, Label = "Directory", Type = FieldType.Textbox, HelpText = "Optional shared folder to put downloads into, leave blank to use the default Download Station location")]
[FieldDefinition(6, Label = "Directory", Type = FieldType.Textbox, HelpText = "DownloadClientDownloadStationSettingsDirectory")]
public string TvDirectory { get; set; }
public DownloadStationSettings()

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using Newtonsoft.Json;
using NzbDrone.Common.Serializer;

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace NzbDrone.Core.Download.Clients.DownloadStation

View File

@@ -1,4 +1,4 @@
using Newtonsoft.Json;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace NzbDrone.Core.Download.Clients.DownloadStation

View File

@@ -1,4 +1,4 @@
using NLog;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Http;
using NzbDrone.Core.Download.Clients.DownloadStation.Responses;

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Http;

View File

@@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Http;

View File

@@ -1,4 +1,4 @@
using Newtonsoft.Json;
using Newtonsoft.Json;
namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses
{

View File

@@ -1,4 +1,4 @@
namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses
namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses
{
public class DiskStationAuthResponse
{

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses
{

View File

@@ -1,4 +1,4 @@
namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses
namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses
{
public class DiskStationResponse<T>
where T : new()

View File

@@ -1,4 +1,4 @@
namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses
namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses
{
public class DownloadStation2SettingsLocationResponse
{

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses
{

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses
{

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses
{

View File

@@ -1,4 +1,4 @@
using System;
using System;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Crypto;

View File

@@ -1,4 +1,4 @@
using NzbDrone.Common.Disk;
using NzbDrone.Common.Disk;
namespace NzbDrone.Core.Download.Clients.DownloadStation
{

View File

@@ -1,4 +1,4 @@
using System;
using System;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Disk;

View File

@@ -11,6 +11,7 @@ using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@@ -37,9 +38,10 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
{
_dsInfoProxy = dsInfoProxy;
_dsTaskProxySelector = dsTaskProxySelector;
@@ -50,7 +52,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
public override string Name => "Download Station";
public override ProviderMessage Message => new ProviderMessage("Radarr is unable to connect to Download Station if 2-Factor Authentication is enabled on your DSM account", ProviderMessageType.Warning);
public override ProviderMessage Message => new ProviderMessage(_localizationService.GetLocalizedString("DownloadClientDownloadStationProviderMessage"), ProviderMessageType.Warning);
private IDownloadStationTaskProxy DsTaskProxy => _dsTaskProxySelector.GetProxy(Settings);
@@ -89,7 +91,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
var item = new DownloadClientItem()
{
Category = Settings.TvCategory,
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this),
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this, false),
DownloadId = CreateDownloadId(torrent.Id, serialNumber),
Title = torrent.Title,
TotalSize = torrent.Size,
@@ -224,7 +226,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{
if (torrent.Status == DownloadStationTaskStatus.Extracting)
{
return $"Extracting: {int.Parse(torrent.StatusExtra["unzip_progress"])}%";
return _localizationService.GetLocalizedString("DownloadStationStatusExtracting",
new Dictionary<string, object>
{ { "progress", int.Parse(torrent.StatusExtra["unzip_progress"]) } });
}
if (torrent.Status == DownloadStationTaskStatus.Error)
@@ -306,53 +310,49 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{
try
{
var downloadDir = GetDefaultDir();
var downloadDir = GetDownloadDirectory();
if (downloadDir == null)
{
return new NzbDroneValidationFailure(nameof(Settings.TvDirectory), "No default destination")
return new NzbDroneValidationFailure(nameof(Settings.TvDirectory), _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationNoDefaultDestination"))
{
DetailedDescription = $"You must login into your Diskstation as {Settings.Username} and manually set it up into DownloadStation settings under BT/HTTP/FTP/NZB -> Location."
DetailedDescription = _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationNoDefaultDestinationDetail", new Dictionary<string, object> { { "username", Settings.Username } })
};
}
downloadDir = GetDownloadDirectory();
var sharedFolder = downloadDir.Split('\\', '/')[0];
var fieldName = Settings.TvDirectory.IsNotNullOrWhiteSpace() ? nameof(Settings.TvDirectory) : nameof(Settings.TvCategory);
if (downloadDir != null)
var folderInfo = _fileStationProxy.GetInfoFileOrDirectory($"/{downloadDir}", Settings);
if (folderInfo.Additional == null)
{
var sharedFolder = downloadDir.Split('\\', '/')[0];
var fieldName = Settings.TvDirectory.IsNotNullOrWhiteSpace() ? nameof(Settings.TvDirectory) : nameof(Settings.TvCategory);
var folderInfo = _fileStationProxy.GetInfoFileOrDirectory($"/{downloadDir}", Settings);
if (folderInfo.Additional == null)
return new NzbDroneValidationFailure(fieldName, _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationSharedFolderMissing"))
{
return new NzbDroneValidationFailure(fieldName, $"Shared folder does not exist")
{
DetailedDescription = $"The Diskstation does not have a Shared Folder with the name '{sharedFolder}', are you sure you specified it correctly?"
};
}
DetailedDescription = _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationSharedFolderMissingDetail", new Dictionary<string, object> { { "sharedFolder", sharedFolder } })
};
}
if (!folderInfo.IsDir)
if (!folderInfo.IsDir)
{
return new NzbDroneValidationFailure(fieldName, _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationFolderMissing"))
{
return new NzbDroneValidationFailure(fieldName, $"Folder does not exist")
{
DetailedDescription = $"The folder '{downloadDir}' does not exist, it must be created manually inside the Shared Folder '{sharedFolder}'."
};
}
DetailedDescription = _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationFolderMissingDetail", new Dictionary<string, object> { { "downloadDir", downloadDir }, { "sharedFolder", sharedFolder } })
};
}
return null;
}
catch (DownloadClientAuthenticationException ex)
{
// User could not have permission to access to downloadstation
_logger.Error(ex, ex.Message);
return new NzbDroneValidationFailure(string.Empty, ex.Message);
}
catch (Exception ex)
{
_logger.Error(ex, "Error testing Torrent Download Station");
return new NzbDroneValidationFailure(string.Empty, $"Unknown exception: {ex.Message}");
return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationUnknownException", new Dictionary<string, object> { { "exception", ex.Message } }));
}
}
@@ -365,9 +365,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
catch (DownloadClientAuthenticationException ex)
{
_logger.Error(ex, ex.Message);
return new NzbDroneValidationFailure("Username", "Authentication failure")
return new NzbDroneValidationFailure("Username", _localizationService.GetLocalizedString("DownloadClientValidationAuthenticationFailure"))
{
DetailedDescription = $"Please verify your username and password. Also verify if the host running Radarr isn't blocked from accessing {Name} by WhiteList limitations in the {Name} configuration."
DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationAuthenticationFailureDetail", new Dictionary<string, object> { { "clientName", Name } })
};
}
catch (WebException ex)
@@ -376,19 +376,19 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
if (ex.Status == WebExceptionStatus.ConnectFailure)
{
return new NzbDroneValidationFailure("Host", "Unable to connect")
return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } }))
{
DetailedDescription = "Please verify the hostname and port."
DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnectDetail")
};
}
return new NzbDroneValidationFailure(string.Empty, $"Unknown exception: {ex.Message}");
return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationUnknownException", new Dictionary<string, object> { { "exception", ex.Message } }));
}
catch (Exception ex)
{
_logger.Error(ex, "Error testing Torrent Download Station");
return new NzbDroneValidationFailure("Host", "Unable to connect to Torrent Download Station")
return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } }))
{
DetailedDescription = ex.Message
};
@@ -403,7 +403,12 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
if (info.MinVersion > 2 || info.MaxVersion < 2)
{
return new ValidationFailure(string.Empty, $"Download Station API version not supported, should be at least 2. It supports from {info.MinVersion} to {info.MaxVersion}");
return new ValidationFailure(string.Empty,
_localizationService.GetLocalizedString("DownloadClientDownloadStationValidationApiVersion",
new Dictionary<string, object>
{
{ "requiredVersion", 2 }, { "minVersion", info.MinVersion }, { "maxVersion", info.MaxVersion }
}));
}
return null;
@@ -418,7 +423,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
}
catch (Exception ex)
{
return new NzbDroneValidationFailure(string.Empty, $"Failed to get the list of torrents: {ex.Message}");
return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationTestTorrents", new Dictionary<string, object> { { "exceptionMessage", ex.Message } }));
}
}
@@ -450,7 +455,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
var destDir = GetDefaultDir();
if (Settings.TvCategory.IsNotNullOrWhiteSpace())
if (destDir.IsNotNullOrWhiteSpace() && Settings.TvCategory.IsNotNullOrWhiteSpace())
{
return $"{destDir.TrimEnd('/')}/{Settings.TvCategory}";
}

View File

@@ -9,6 +9,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.ThingiProvider;
@@ -34,8 +35,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IValidateNzbs nzbValidationService,
Logger logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger)
Logger logger,
ILocalizationService localizationService)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger, localizationService)
{
_dsInfoProxy = dsInfoProxy;
_dsTaskProxySelector = dsTaskProxySelector;
@@ -46,7 +48,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
public override string Name => "Download Station";
public override ProviderMessage Message => new ProviderMessage("Radarr is unable to connect to Download Station if 2-Factor Authentication is enabled on your DSM account", ProviderMessageType.Warning);
public override ProviderMessage Message => new ProviderMessage(_localizationService.GetLocalizedString("DownloadClientDownloadStationProviderMessage"), ProviderMessageType.Warning);
private IDownloadStationTaskProxy DsTaskProxy => _dsTaskProxySelector.GetProxy(Settings);
@@ -97,7 +99,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
var item = new DownloadClientItem()
{
Category = Settings.TvCategory,
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this),
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this, false),
DownloadId = CreateDownloadId(nzb.Id, serialNumber),
Title = nzb.Title,
TotalSize = nzb.Size,
@@ -209,53 +211,50 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{
try
{
var downloadDir = GetDefaultDir();
var downloadDir = GetDownloadDirectory();
if (downloadDir == null)
{
return new NzbDroneValidationFailure(nameof(Settings.TvDirectory), "No default destination")
return new NzbDroneValidationFailure(nameof(Settings.TvDirectory), _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationNoDefaultDestination"))
{
DetailedDescription = $"You must login into your Diskstation as {Settings.Username} and manually set it up into DownloadStation settings under BT/HTTP/FTP/NZB -> Location."
DetailedDescription = _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationNoDefaultDestinationDetail", new Dictionary<string, object> { { "username", Settings.Username } })
};
}
downloadDir = GetDownloadDirectory();
var sharedFolder = downloadDir.Split('\\', '/')[0];
var fieldName = Settings.TvDirectory.IsNotNullOrWhiteSpace() ? nameof(Settings.TvDirectory) : nameof(Settings.TvCategory);
if (downloadDir != null)
var folderInfo = _fileStationProxy.GetInfoFileOrDirectory($"/{downloadDir}", Settings);
if (folderInfo.Additional == null)
{
var sharedFolder = downloadDir.Split('\\', '/')[0];
var fieldName = Settings.TvDirectory.IsNotNullOrWhiteSpace() ? nameof(Settings.TvDirectory) : nameof(Settings.TvCategory);
var folderInfo = _fileStationProxy.GetInfoFileOrDirectory($"/{downloadDir}", Settings);
if (folderInfo.Additional == null)
return new NzbDroneValidationFailure(fieldName, _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationSharedFolderMissing"))
{
return new NzbDroneValidationFailure(fieldName, $"Shared folder does not exist")
{
DetailedDescription = $"The Diskstation does not have a Shared Folder with the name '{sharedFolder}', are you sure you specified it correctly?"
};
}
DetailedDescription = _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationSharedFolderMissingDetail",
new Dictionary<string, object> { { "sharedFolder", sharedFolder } })
};
}
if (!folderInfo.IsDir)
if (!folderInfo.IsDir)
{
return new NzbDroneValidationFailure(fieldName, _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationFolderMissing"))
{
return new NzbDroneValidationFailure(fieldName, $"Folder does not exist")
{
DetailedDescription = $"The folder '{downloadDir}' does not exist, it must be created manually inside the Shared Folder '{sharedFolder}'."
};
}
DetailedDescription = _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationFolderMissingDetail", new Dictionary<string, object> { { "downloadDir", downloadDir }, { "sharedFolder", sharedFolder } })
};
}
return null;
}
catch (DownloadClientAuthenticationException ex)
{
// User could not have permission to access to downloadstation
_logger.Error(ex, ex.Message);
return new NzbDroneValidationFailure(string.Empty, ex.Message);
}
catch (Exception ex)
{
_logger.Error(ex, "Error testing Usenet Download Station");
return new NzbDroneValidationFailure(string.Empty, $"Unknown exception: {ex.Message}");
return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationUnknownException", new Dictionary<string, object> { { "exception", ex.Message } }));
}
}
@@ -268,9 +267,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
catch (DownloadClientAuthenticationException ex)
{
_logger.Error(ex, ex.Message);
return new NzbDroneValidationFailure("Username", "Authentication failure")
return new NzbDroneValidationFailure("Username", _localizationService.GetLocalizedString("DownloadClientValidationAuthenticationFailure"))
{
DetailedDescription = $"Please verify your username and password. Also verify if the host running Radarr isn't blocked from accessing {Name} by WhiteList limitations in the {Name} configuration."
DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationAuthenticationFailureDetail", new Dictionary<string, object> { { "clientName", Name } })
};
}
catch (WebException ex)
@@ -279,19 +278,19 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
if (ex.Status == WebExceptionStatus.ConnectFailure)
{
return new NzbDroneValidationFailure("Host", "Unable to connect")
return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } }))
{
DetailedDescription = "Please verify the hostname and port."
DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnectDetail")
};
}
return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message);
return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationUnknownException", new Dictionary<string, object> { { "exception", ex.Message } }));
}
catch (Exception ex)
{
_logger.Error(ex, "Error testing Torrent Download Station");
return new NzbDroneValidationFailure("Host", "Unable to connect to Usenet Download Station")
return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } }))
{
DetailedDescription = ex.Message
};
@@ -306,7 +305,12 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
if (info.MinVersion > 2 || info.MaxVersion < 2)
{
return new ValidationFailure(string.Empty, $"Download Station API version not supported, should be at least 2. It supports from {info.MinVersion} to {info.MaxVersion}");
return new ValidationFailure(string.Empty,
_localizationService.GetLocalizedString("DownloadClientDownloadStationValidationApiVersion",
new Dictionary<string, object>
{
{ "requiredVersion", 2 }, { "minVersion", info.MinVersion }, { "maxVersion", info.MaxVersion }
}));
}
return null;
@@ -318,7 +322,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{
if (task.Status == DownloadStationTaskStatus.Extracting)
{
return $"Extracting: {int.Parse(task.StatusExtra["unzip_progress"])}%";
return _localizationService.GetLocalizedString("DownloadStationStatusExtracting",
new Dictionary<string, object>
{ { "progress", int.Parse(task.StatusExtra["unzip_progress"]) } });
}
if (task.Status == DownloadStationTaskStatus.Error)
@@ -397,7 +403,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
}
catch (Exception ex)
{
return new NzbDroneValidationFailure(string.Empty, "Failed to get the list of NZBs: " + ex.Message);
return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationTestNzbs", new Dictionary<string, object> { { "exceptionMessage", ex.Message } }));
}
}
@@ -429,7 +435,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
var destDir = GetDefaultDir();
if (Settings.TvCategory.IsNotNullOrWhiteSpace())
if (destDir.IsNotNullOrWhiteSpace() && Settings.TvCategory.IsNotNullOrWhiteSpace())
{
return $"{destDir.TrimEnd('/')}/{Settings.TvCategory}";
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using FluentValidation.Results;
using NLog;
@@ -9,6 +10,7 @@ using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.Flood.Models;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@@ -28,9 +30,10 @@ namespace NzbDrone.Core.Download.Clients.Flood
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
{
_proxy = proxy;
_downloadSeedConfigProvider = downloadSeedConfigProvider;
@@ -82,7 +85,8 @@ namespace NzbDrone.Core.Download.Clients.Flood
}
public override string Name => "Flood";
public override ProviderMessage Message => new ProviderMessage("Radarr will handle automatic removal of torrents based on the current seed criteria in Settings -> Indexers", ProviderMessageType.Info);
public override ProviderMessage Message => new ProviderMessage(_localizationService.GetLocalizedString("DownloadClientFloodSettingsRemovalInfo"), ProviderMessageType.Info);
protected override string AddFromTorrentFile(RemoteMovie remoteMovie, string hash, string filename, byte[] fileContent)
{
_proxy.AddTorrentByFile(Convert.ToBase64String(fileContent), HandleTags(remoteMovie, Settings), Settings);
@@ -114,7 +118,7 @@ namespace NzbDrone.Core.Download.Clients.Flood
var item = new DownloadClientItem
{
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this),
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this, false),
DownloadId = torrent.Key,
Title = properties.Name,
OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(properties.Directory)),
@@ -221,7 +225,7 @@ namespace NzbDrone.Core.Download.Clients.Flood
if (list.ContainsKey(downloadClientItem.DownloadId))
{
_proxy.SetTorrentsTags(downloadClientItem.DownloadId,
list[downloadClientItem.DownloadId].Tags.Concat(Settings.PostImportTags).ToHashSet(),
list[downloadClientItem.DownloadId].Tags.Concat(Settings.PostImportTags).ToImmutableHashSet(),
Settings);
}
}

View File

@@ -40,10 +40,12 @@ namespace NzbDrone.Core.Download.Clients.Flood
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Flood")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
[FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Flood")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, HelpText = "Optionally adds a prefix to Flood API, such as [protocol]://[host]:[port]/[urlBase]api")]
[FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, HelpText = "DownloadClientFloodSettingsUrlBaseHelpText")]
[FieldToken(TokenField.HelpText, "UrlBase", "url", "[protocol]://[host]:[port]/[urlBase]/api")]
public string UrlBase { get; set; }
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
@@ -52,19 +54,19 @@ namespace NzbDrone.Core.Download.Clients.Flood
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(6, Label = "Destination", Type = FieldType.Textbox, HelpText = "Manually specifies download destination")]
[FieldDefinition(6, Label = "Destination", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDestinationHelpText")]
public string Destination { get; set; }
[FieldDefinition(7, Label = "Tags", Type = FieldType.Tag, HelpText = "Initial tags of a download. To be recognized, a download must have all initial tags. This avoids conflicts with unrelated downloads.")]
[FieldDefinition(7, Label = "Tags", Type = FieldType.Tag, HelpText = "DownloadClientFloodSettingsTagsHelpText")]
public IEnumerable<string> Tags { get; set; }
[FieldDefinition(8, Label = "Post-Import Tags", Type = FieldType.Tag, HelpText = "Appends tags after a download is imported.", Advanced = true)]
[FieldDefinition(8, Label = "DownloadClientFloodSettingsPostImportTags", Type = FieldType.Tag, HelpText = "DownloadClientFloodSettingsPostImportTagsHelpText", Advanced = true)]
public IEnumerable<string> PostImportTags { get; set; }
[FieldDefinition(9, Label = "Additional Tags", Type = FieldType.Select, SelectOptions = typeof(AdditionalTags), HelpText = "Adds properties of media as tags. Hints are examples.", Advanced = true)]
[FieldDefinition(9, Label = "DownloadClientFloodSettingsAdditionalTags", Type = FieldType.Select, SelectOptions = typeof(AdditionalTags), HelpText = "DownloadClientFloodSettingsAdditionalTagsHelpText", Advanced = true)]
public IEnumerable<int> AdditionalTags { get; set; }
[FieldDefinition(10, Label = "Add Paused", Type = FieldType.Checkbox)]
[FieldDefinition(10, Label = "DownloadClientFloodSettingsAddPaused", Type = FieldType.Checkbox)]
public bool AddPaused { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -1,4 +1,4 @@
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
{
public class FreeboxDownloadException : DownloadClientException
{

View File

@@ -1,4 +1,4 @@
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
{
public enum FreeboxDownloadPriority
{

View File

@@ -46,37 +46,42 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
ApiUrl = "/api/v1/";
}
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox, HelpText = "Hostname or host IP address of the Freebox, defaults to 'mafreebox.freebox.fr' (will only work if on same network)")]
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox, HelpText = "DownloadClientFreeboxSettingsHostHelpText")]
[FieldToken(TokenField.HelpText, "Host", "url", "mafreebox.freebox.fr")]
public string Host { get; set; }
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox, HelpText = "Port used to access Freebox interface, defaults to '443'")]
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox, HelpText = "DownloadClientFreeboxSettingsPortHelpText")]
[FieldToken(TokenField.HelpText, "Port", "port", 443)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secured connection when connecting to Freebox API")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
[FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Freebox API")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "API URL", Type = FieldType.Textbox, Advanced = true, HelpText = "Define Freebox API base URL with API version, eg http://[host]:[port]/[api_base_url]/[api_version]/, defaults to '/api/v1/'")]
[FieldDefinition(3, Label = "DownloadClientFreeboxSettingsApiUrl", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientFreeboxSettingsApiUrlHelpText")]
[FieldToken(TokenField.HelpText, "DownloadClientFreeboxSettingsApiUrl", "url", "http://[host]:[port]/[api_base_url]/[api_version]/")]
[FieldToken(TokenField.HelpText, "DownloadClientFreeboxSettingsApiUrl", "defaultApiUrl", "/api/v1/")]
public string ApiUrl { get; set; }
[FieldDefinition(4, Label = "App ID", Type = FieldType.Textbox, HelpText = "App ID given when creating access to Freebox API (ie 'app_id')")]
[FieldDefinition(4, Label = "DownloadClientFreeboxSettingsAppId", Type = FieldType.Textbox, HelpText = "DownloadClientFreeboxSettingsAppIdHelpText")]
public string AppId { get; set; }
[FieldDefinition(5, Label = "App Token", Type = FieldType.Password, Privacy = PrivacyLevel.Password, HelpText = "App token retrieved when creating access to Freebox API (ie 'app_token')")]
[FieldDefinition(5, Label = "DownloadClientFreeboxSettingsAppToken", Type = FieldType.Password, Privacy = PrivacyLevel.Password, HelpText = "DownloadClientFreeboxSettingsAppTokenHelpText")]
public string AppToken { get; set; }
[FieldDefinition(6, Label = "Destination Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default Freebox download location")]
[FieldDefinition(6, Label = "Destination", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsDestinationHelpText")]
public string DestinationDirectory { get; set; }
[FieldDefinition(7, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated non-Radarr downloads (will create a [category] subdirectory in the output directory)")]
[FieldDefinition(7, Label = "Category", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategorySubFolderHelpText")]
public string Category { get; set; }
[FieldDefinition(8, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
[FieldDefinition(8, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "DownloadClientSettingsRecentPriorityMovieHelpText")]
public int RecentPriority { get; set; }
[FieldDefinition(9, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
[FieldDefinition(9, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "DownloadClientSettingsOlderPriorityMovieHelpText")]
public int OlderPriority { get; set; }
[FieldDefinition(10, Label = "Add Paused", Type = FieldType.Checkbox)]
[FieldDefinition(10, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox)]
public bool AddPaused { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -1,4 +1,4 @@
using Newtonsoft.Json;
using Newtonsoft.Json;
namespace NzbDrone.Core.Download.Clients.FreeboxDownload.Responses
{

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using Newtonsoft.Json;
using NzbDrone.Common.Serializer;

View File

@@ -1,4 +1,4 @@
using Newtonsoft.Json;
using Newtonsoft.Json;
namespace NzbDrone.Core.Download.Clients.FreeboxDownload.Responses
{

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace NzbDrone.Core.Download.Clients.FreeboxDownload.Responses

View File

@@ -9,6 +9,7 @@ using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.FreeboxDownload.Responses;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@@ -25,9 +26,10 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
{
_proxy = proxy;
}
@@ -73,7 +75,7 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
Category = Settings.Category,
Title = torrent.Name,
TotalSize = torrent.Size,
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this),
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this, false),
RemainingSize = (long)(torrent.Size * (double)(1 - ((double)torrent.ReceivedPrct / 10000))),
RemainingTime = torrent.Eta <= 0 ? null : TimeSpan.FromSeconds(torrent.Eta),
SeedRatio = torrent.StopRatio <= 0 ? 0 : torrent.StopRatio / 100,
@@ -110,8 +112,9 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
case FreeboxDownloadTaskStatus.Unknown:
default: // new status in API? default to downloading
item.Message = "Unknown download state: " + torrent.Status;
_logger.Info(item.Message);
item.Message = _localizationService.GetLocalizedString("UnknownDownloadState",
new Dictionary<string, object> { { "state", torrent.Status } });
_logger.Info($"Unknown download state: {torrent.Status}");
item.Status = DownloadItemStatus.Downloading;
break;
}

View File

@@ -9,6 +9,7 @@ using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.Hadouken.Models;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@@ -26,9 +27,10 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
{
_proxy = proxy;
}
@@ -68,7 +70,7 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
var item = new DownloadClientItem
{
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this),
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this, false),
DownloadId = torrent.InfoHash.ToUpper(),
OutputPath = outputPath + torrent.Name,
RemainingSize = torrent.TotalSize - torrent.DownloadedBytes,
@@ -172,18 +174,20 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
if (version < new Version("5.1"))
{
return new ValidationFailure(string.Empty,
"Old Hadouken client with unsupported API, need 5.1 or higher");
_localizationService.GetLocalizedString("DownloadClientValidationErrorVersion",
new Dictionary<string, object>
{ { "clientName", Name }, { "requiredVersion", "5.1" }, { "reportedVersion", version } }));
}
}
catch (DownloadClientAuthenticationException ex)
{
_logger.Error(ex, ex.Message);
return new NzbDroneValidationFailure("Password", "Authentication failed");
return new NzbDroneValidationFailure("Password", _localizationService.GetLocalizedString("DownloadClientValidationAuthenticationFailure"));
}
catch (Exception ex)
{
return new NzbDroneValidationFailure("Host", "Unable to connect to Hadouken")
return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect"))
{
DetailedDescription = ex.Message
};
@@ -201,7 +205,7 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
catch (Exception ex)
{
_logger.Error(ex, ex.Message);
return new NzbDroneValidationFailure(string.Empty, "Failed to get the list of torrents: " + ex.Message);
return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationTestTorrents", new Dictionary<string, object> { { "exceptionMessage", ex.Message } }));
}
return null;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Net;
using NLog;

View File

@@ -39,10 +39,13 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Hadouken")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
[FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Hadouken")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the Hadouken url, e.g. http://[host]:[port]/[urlBase]/api")]
[FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsUrlBaseHelpText")]
[FieldToken(TokenField.HelpText, "UrlBase", "clientName", "Hadouken")]
[FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/api")]
public string UrlBase { get; set; }
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
@@ -51,7 +54,7 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox)]
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategoryHelpText")]
public string Category { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
namespace NzbDrone.Core.Download.Clients.Hadouken.Models
{

View File

@@ -1,4 +1,4 @@
namespace NzbDrone.Core.Download.Clients.Hadouken.Models
namespace NzbDrone.Core.Download.Clients.Hadouken.Models
{
public sealed class HadoukenTorrent
{

View File

@@ -1,4 +1,4 @@
namespace NzbDrone.Core.Download.Clients.Hadouken.Models
namespace NzbDrone.Core.Download.Clients.Hadouken.Models
{
public class HadoukenTorrentResponse
{

View File

@@ -1,4 +1,4 @@
namespace NzbDrone.Core.Download.Clients.Hadouken.Models
namespace NzbDrone.Core.Download.Clients.Hadouken.Models
{
public enum HadoukenTorrentState
{

View File

@@ -8,6 +8,7 @@ using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation;
@@ -24,8 +25,9 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IValidateNzbs nzbValidationService,
Logger logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger)
Logger logger,
ILocalizationService localizationService)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger, localizationService)
{
_proxy = proxy;
}
@@ -66,7 +68,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
{
var queueItem = new DownloadClientItem();
queueItem.DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this);
queueItem.DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this, false);
queueItem.DownloadId = vortexQueueItem.AddUUID ?? vortexQueueItem.Id.ToString();
queueItem.Category = vortexQueueItem.GroupName;
queueItem.Title = vortexQueueItem.UiTitle;
@@ -172,7 +174,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
{
_logger.Error(ex, "Unable to connect to NZBVortex");
return new NzbDroneValidationFailure("Host", "Unable to connect to NZBVortex")
return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } }))
{
DetailedDescription = ex.Message
};
@@ -190,13 +192,16 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
if (version.Major < 2 || (version.Major == 2 && version.Minor < 3))
{
return new ValidationFailure("Host", "NZBVortex needs to be updated");
return new ValidationFailure("Host",
_localizationService.GetLocalizedString("DownloadClientValidationErrorVersion",
new Dictionary<string, object>
{ { "clientName", Name }, { "requiredVersion", "2.3" }, { "reportedVersion", version } }));
}
}
catch (Exception ex)
{
_logger.Error(ex, ex.Message);
return new ValidationFailure("Host", "Unable to connect to NZBVortex");
_logger.Error(ex, "Unable to connect to NZBVortex");
return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } }));
}
return null;
@@ -210,7 +215,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
}
catch (NzbVortexAuthenticationException)
{
return new ValidationFailure("ApiKey", "API Key Incorrect");
return new ValidationFailure("ApiKey", _localizationService.GetLocalizedString("DownloadClientValidationApiKeyIncorrect"));
}
return null;
@@ -224,9 +229,9 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
{
if (Settings.TvCategory.IsNotNullOrWhiteSpace())
{
return new NzbDroneValidationFailure("TvCategory", "Group does not exist")
return new NzbDroneValidationFailure("TvCategory", _localizationService.GetLocalizedString("DownloadClientValidationGroupMissing"))
{
DetailedDescription = "The Group you entered doesn't exist in NzbVortex. Go to NzbVortex to create it."
DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationGroupMissingDetail", new Dictionary<string, object> { { "clientName", Name } })
};
}
}
@@ -253,7 +258,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
if (filesResponse.Count > 1)
{
var message = string.Format("Download contains multiple files and is not in a job folder: {0}", outputPath);
var message = _localizationService.GetLocalizedString("DownloadClientNzbVortexMultipleFilesMessage", new Dictionary<string, object> { { "outputPath", outputPath } });
queueItem.Status = DownloadItemStatus.Warning;
queueItem.Message = message;

View File

@@ -1,4 +1,4 @@
using System;
using System;
namespace NzbDrone.Core.Download.Clients.NzbVortex
{

Some files were not shown because too many files have changed in this diff Show More