mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-18 21:34:28 -04:00
Compare commits
137 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cf415e61de | |||
| 9865e92cea | |||
| 1cf956a9d9 | |||
| 8989c55c8c | |||
| dc83e0127e | |||
| 34eb312426 | |||
| 9d5cdebdb2 | |||
| a0ab224acd | |||
| 05aa35a54d | |||
| ca7f8775f5 | |||
| 2a01e9b445 | |||
| 7d30c7d1ea | |||
| 50be87e5a4 | |||
| 0572d1ac80 | |||
| d2240514d7 | |||
| ad47dc032d | |||
| 6c6df7d7d9 | |||
| 2121204064 | |||
| 61004ea33f | |||
| 54c1c7862e | |||
| 43dfdc8bf5 | |||
| 0d1ae0ca4e | |||
| 9902889a30 | |||
| 04d7061030 | |||
| fd201912a9 | |||
| c412701a3d | |||
| 7451a66365 | |||
| a6431fdb0b | |||
| 060b133f6d | |||
| 5ed13b942b | |||
| 89f3d8167b | |||
| 77b027374f | |||
| 650490abb2 | |||
| 7d2e215d61 | |||
| 65ff890c74 | |||
| 50c0b0dbaa | |||
| d5f36d0144 | |||
| fab7558bd4 | |||
| 3dc86b3a01 | |||
| 24ad6134e3 | |||
| 033f8c40af | |||
| 4c73a619eb | |||
| 3ca798e983 | |||
| d9827fd6a6 | |||
| f4f03a853f | |||
| 4f4e4bf2ca | |||
| 413a70a312 | |||
| a8f2b91010 | |||
| 68a4ee6000 | |||
| 5196ce311b | |||
| ae92b22727 | |||
| 0bccffef01 | |||
| bca899b9c0 | |||
| 2bb576a94b | |||
| bb49949853 | |||
| a093061b29 | |||
| df876707c4 | |||
| 2af33143ba | |||
| c3c5a47776 | |||
| a21abe0838 | |||
| a32f5f6639 | |||
| 4cd45ecc21 | |||
| 2c8e0b1ca4 | |||
| bd25c9e3e0 | |||
| ee64b8788b | |||
| 7aeada2089 | |||
| e188c9aac0 | |||
| a3ae2359f5 | |||
| 5b92905dd4 | |||
| fc402743aa | |||
| b9d53ed732 | |||
| d248747635 | |||
| d70224c811 | |||
| acdf8c8aa8 | |||
| 3ed41554ce | |||
| ce808c6d7b | |||
| 63b1b56a4f | |||
| a5647bedc8 | |||
| fe659bb79d | |||
| 9918535509 | |||
| f9a6db40b8 | |||
| 6273d69ed6 | |||
| 7012380e95 | |||
| b001ecd698 | |||
| e28becdda4 | |||
| eae06695e8 | |||
| 54a9af2ced | |||
| c9b55266fc | |||
| 05b64406a4 | |||
| 1f37c5387b | |||
| 4a6c7042fe | |||
| d7305b9753 | |||
| bd56643eaa | |||
| 44e6de2e23 | |||
| b209d047fa | |||
| fd5ab27df6 | |||
| 4a89befd79 | |||
| 1a30293c33 | |||
| f5c2a6bf51 | |||
| f3d90fdaf1 | |||
| 04c5671a0a | |||
| 22cc88c5e7 | |||
| ca0c95a2d2 | |||
| 419f790d66 | |||
| 9fe08429bc | |||
| 71f4a88ab3 | |||
| 30b283eda3 | |||
| e23d0bbfa1 | |||
| 765a2aa01b | |||
| 64895c3210 | |||
| 03ab84a814 | |||
| b77e5b14e1 | |||
| 75efbd45e1 | |||
| 00cac507ad | |||
| c4850505b0 | |||
| 75213c86a1 | |||
| b8c3a42643 | |||
| 8acb034aa6 | |||
| 889d32552b | |||
| adc5f4db97 | |||
| 9d08050f96 | |||
| f8cffbb4cf | |||
| 14aeb66142 | |||
| 37e8e11e31 | |||
| bdb2f14936 | |||
| a97af657be | |||
| 301127e6dc | |||
| 1f95bcae4e | |||
| 29118cda45 | |||
| 09beaa939d | |||
| 2107624f1c | |||
| c1c2076e5c | |||
| c31a797bd8 | |||
| ebb2b4eca3 | |||
| 3ec5d9b9fe | |||
| 1ad84a7c44 | |||
| 9d67c18254 |
@@ -1,5 +1,5 @@
|
|||||||
name: Bug Report
|
name: Bug Report
|
||||||
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Reddit or Discord first'
|
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Discord first'
|
||||||
labels: ['Type: Bug', 'Status: Needs Triage']
|
labels: ['Type: Bug', 'Status: Needs Triage']
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
|
|||||||
@@ -3,6 +3,3 @@ contact_links:
|
|||||||
- name: Support via Discord
|
- name: Support via Discord
|
||||||
url: https://readarr.com/discord
|
url: https://readarr.com/discord
|
||||||
about: Chat with users and devs on support and setup related topics.
|
about: Chat with users and devs on support and setup related topics.
|
||||||
- name: Support via Reddit
|
|
||||||
url: https://reddit.com/r/Readarr
|
|
||||||
about: Discuss and search thru support topics.
|
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ jobs:
|
|||||||
issue-comment: >
|
issue-comment: >
|
||||||
:wave: @{issue-author}, we use the issue tracker exclusively
|
:wave: @{issue-author}, we use the issue tracker exclusively
|
||||||
for bug reports and feature requests. However, this issue appears
|
for bug reports and feature requests. However, this issue appears
|
||||||
to be a support request. Please hop over onto our [Discord](https://readarr.com/discord)
|
to be a support request. Please hop over onto our [Discord](https://readarr.com/discord).
|
||||||
or [Subreddit](https://reddit.com/r/readarr)
|
|
||||||
close-issue: true
|
close-issue: true
|
||||||
lock-issue: false
|
lock-issue: false
|
||||||
- uses: dessant/support-requests@v3
|
- uses: dessant/support-requests@v3
|
||||||
|
|||||||
+2
-2
@@ -9,13 +9,13 @@ variables:
|
|||||||
testsFolder: './_tests'
|
testsFolder: './_tests'
|
||||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||||
majorVersion: '0.3.3'
|
majorVersion: '0.3.14'
|
||||||
minorVersion: $[counter('minorVersion', 1)]
|
minorVersion: $[counter('minorVersion', 1)]
|
||||||
readarrVersion: '$(majorVersion).$(minorVersion)'
|
readarrVersion: '$(majorVersion).$(minorVersion)'
|
||||||
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
||||||
sentryOrg: 'servarr'
|
sentryOrg: 'servarr'
|
||||||
sentryUrl: 'https://sentry.servarr.com'
|
sentryUrl: 'https://sentry.servarr.com'
|
||||||
dotnetVersion: '6.0.408'
|
dotnetVersion: '6.0.417'
|
||||||
nodeVersion: '16.X'
|
nodeVersion: '16.X'
|
||||||
innoVersion: '6.2.0'
|
innoVersion: '6.2.0'
|
||||||
windowsImage: 'windows-2022'
|
windowsImage: 'windows-2022'
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ module.exports = {
|
|||||||
plugins: [
|
plugins: [
|
||||||
// Stage 1
|
// Stage 1
|
||||||
'@babel/plugin-proposal-export-default-from',
|
'@babel/plugin-proposal-export-default-from',
|
||||||
['@babel/plugin-proposal-optional-chaining', { loose }],
|
['@babel/plugin-transform-optional-chaining', { loose }],
|
||||||
['@babel/plugin-proposal-nullish-coalescing-operator', { loose }],
|
['@babel/plugin-transform-nullish-coalescing-operator', { loose }],
|
||||||
|
|
||||||
// Stage 2
|
// Stage 2
|
||||||
'@babel/plugin-proposal-export-namespace-from',
|
'@babel/plugin-transform-export-namespace-from',
|
||||||
|
|
||||||
// Stage 3
|
// Stage 3
|
||||||
['@babel/plugin-proposal-class-properties', { loose }],
|
['@babel/plugin-transform-class-properties', { loose }],
|
||||||
'@babel/plugin-syntax-dynamic-import'
|
'@babel/plugin-syntax-dynamic-import'
|
||||||
],
|
],
|
||||||
env: {
|
env: {
|
||||||
|
|||||||
@@ -338,4 +338,8 @@ Queue.propTypes = {
|
|||||||
onRemoveSelectedPress: PropTypes.func.isRequired
|
onRemoveSelectedPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Queue.defaultProps = {
|
||||||
|
count: 0
|
||||||
|
};
|
||||||
|
|
||||||
export default Queue;
|
export default Queue;
|
||||||
|
|||||||
@@ -7,13 +7,10 @@ function findImage(images, coverType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getUrl(image, coverType, size) {
|
function getUrl(image, coverType, size) {
|
||||||
if (image) {
|
const imageUrl = image?.url;
|
||||||
// Remove protocol
|
|
||||||
let url = image.url;
|
|
||||||
|
|
||||||
url = url.replace(`${coverType}.jpg`, `${coverType}-${size}.jpg`);
|
if (imageUrl) {
|
||||||
|
return imageUrl.replace(`${coverType}.jpg`, `${coverType}-${size}.jpg`);
|
||||||
return url;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,10 @@
|
|||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filterIcon {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
.authorNavigationButtons {
|
.authorNavigationButtons {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ interface CssExports {
|
|||||||
'authorUpButton': string;
|
'authorUpButton': string;
|
||||||
'contentContainer': string;
|
'contentContainer': string;
|
||||||
'errorMessage': string;
|
'errorMessage': string;
|
||||||
|
'filterIcon': string;
|
||||||
'innerContentBody': string;
|
'innerContentBody': string;
|
||||||
'metadataMessage': string;
|
'metadataMessage': string;
|
||||||
'selectedTab': string;
|
'selectedTab': string;
|
||||||
|
|||||||
@@ -239,9 +239,14 @@ class AuthorDetails extends Component {
|
|||||||
saveError,
|
saveError,
|
||||||
isDeleting,
|
isDeleting,
|
||||||
deleteError,
|
deleteError,
|
||||||
statistics
|
statistics = {}
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
bookFileCount = 0,
|
||||||
|
totalBookCount = 0
|
||||||
|
} = statistics;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOrganizeModalOpen,
|
isOrganizeModalOpen,
|
||||||
isRetagModalOpen,
|
isRetagModalOpen,
|
||||||
@@ -435,7 +440,7 @@ class AuthorDetails extends Component {
|
|||||||
className={styles.tab}
|
className={styles.tab}
|
||||||
selectedClassName={styles.selectedTab}
|
selectedClassName={styles.selectedTab}
|
||||||
>
|
>
|
||||||
{translate('BooksTotal', [statistics.totalBookCount])}
|
{translate('BooksTotal', [totalBookCount])}
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab
|
<Tab
|
||||||
@@ -463,7 +468,7 @@ class AuthorDetails extends Component {
|
|||||||
className={styles.tab}
|
className={styles.tab}
|
||||||
selectedClassName={styles.selectedTab}
|
selectedClassName={styles.selectedTab}
|
||||||
>
|
>
|
||||||
{translate('FilesTotal', [statistics.bookFileCount])}
|
{translate('FilesTotal', [bookFileCount])}
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -136,8 +136,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
font-weight: 300;
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
line-height: 50px;
|
line-height: 30px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,12 +25,7 @@ const defaultFontSize = parseInt(fonts.defaultFontSize);
|
|||||||
const lineHeight = parseFloat(fonts.lineHeight);
|
const lineHeight = parseFloat(fonts.lineHeight);
|
||||||
|
|
||||||
function getFanartUrl(images) {
|
function getFanartUrl(images) {
|
||||||
const fanartImage = images.find((x) => x.coverType === 'fanart');
|
return images.find((x) => x.coverType === 'fanart')?.url;
|
||||||
|
|
||||||
if (fanartImage) {
|
|
||||||
// Remove protocol
|
|
||||||
return fanartImage.url.replace(/^https?:/, '');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuthorDetailsHeader extends Component {
|
class AuthorDetailsHeader extends Component {
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
.container {
|
||||||
|
border: 1px solid var(--borderColor);
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--inputBackgroundColor);
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// This file is automatically generated.
|
||||||
|
// Please do not change this file!
|
||||||
|
interface CssExports {
|
||||||
|
'container': string;
|
||||||
|
}
|
||||||
|
export const cssExports: CssExports;
|
||||||
|
export default cssExports;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import AuthorHistoryContentConnector from 'Author/History/AuthorHistoryContentConnector';
|
import AuthorHistoryContentConnector from 'Author/History/AuthorHistoryContentConnector';
|
||||||
import AuthorHistoryTableContent from 'Author/History/AuthorHistoryTableContent';
|
import AuthorHistoryTableContent from 'Author/History/AuthorHistoryTableContent';
|
||||||
|
import styles from './AuthorHistoryTable.css';
|
||||||
|
|
||||||
function AuthorHistoryTable(props) {
|
function AuthorHistoryTable(props) {
|
||||||
const {
|
const {
|
||||||
@@ -8,10 +9,12 @@ function AuthorHistoryTable(props) {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthorHistoryContentConnector
|
<div className={styles.container}>
|
||||||
component={AuthorHistoryTableContent}
|
<AuthorHistoryContentConnector
|
||||||
{...otherProps}
|
component={AuthorHistoryTableContent}
|
||||||
/>
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.blankpad {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// This file is automatically generated.
|
||||||
|
// Please do not change this file!
|
||||||
|
interface CssExports {
|
||||||
|
'blankpad': string;
|
||||||
|
}
|
||||||
|
export const cssExports: CssExports;
|
||||||
|
export default cssExports;
|
||||||
@@ -7,6 +7,7 @@ import TableBody from 'Components/Table/TableBody';
|
|||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import AuthorHistoryRowConnector from './AuthorHistoryRowConnector';
|
import AuthorHistoryRowConnector from './AuthorHistoryRowConnector';
|
||||||
|
import styles from './AuthorHistoryTableContent.css';
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@@ -64,7 +65,7 @@ class AuthorHistoryTableContent extends Component {
|
|||||||
const hasItems = !!items.length;
|
const hasItems = !!items.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div>
|
||||||
{
|
{
|
||||||
isFetching &&
|
isFetching &&
|
||||||
<LoadingIndicator />
|
<LoadingIndicator />
|
||||||
@@ -79,7 +80,7 @@ class AuthorHistoryTableContent extends Component {
|
|||||||
|
|
||||||
{
|
{
|
||||||
isPopulated && !hasItems && !error &&
|
isPopulated && !hasItems && !error &&
|
||||||
<div>
|
<div className={styles.blankpad}>
|
||||||
{translate('NoHistory')}
|
{translate('NoHistory')}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -103,7 +104,7 @@ class AuthorHistoryTableContent extends Component {
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
}
|
}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import AuthorIndex from './AuthorIndex';
|
|||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createAuthorClientSideCollectionItemsSelector('authorIndex'),
|
createAuthorClientSideCollectionItemsSelector('authorIndex'),
|
||||||
createCommandExecutingSelector(commandNames.REFRESH_AUTHOR),
|
createCommandExecutingSelector(commandNames.BULK_REFRESH_AUTHOR),
|
||||||
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
||||||
createCommandExecutingSelector(commandNames.RENAME_AUTHOR),
|
createCommandExecutingSelector(commandNames.RENAME_AUTHOR),
|
||||||
createCommandExecutingSelector(commandNames.RETAG_AUTHOR),
|
createCommandExecutingSelector(commandNames.RETAG_AUTHOR),
|
||||||
@@ -24,17 +24,17 @@ function createMapStateToProps() {
|
|||||||
(
|
(
|
||||||
author,
|
author,
|
||||||
isRefreshingAuthor,
|
isRefreshingAuthor,
|
||||||
|
isRssSyncExecuting,
|
||||||
isOrganizingAuthor,
|
isOrganizingAuthor,
|
||||||
isRetaggingAuthor,
|
isRetaggingAuthor,
|
||||||
isRssSyncExecuting,
|
|
||||||
dimensionsState
|
dimensionsState
|
||||||
) => {
|
) => {
|
||||||
return {
|
return {
|
||||||
...author,
|
...author,
|
||||||
isRefreshingAuthor,
|
isRefreshingAuthor,
|
||||||
|
isRssSyncExecuting,
|
||||||
isOrganizingAuthor,
|
isOrganizingAuthor,
|
||||||
isRetaggingAuthor,
|
isRetaggingAuthor,
|
||||||
isRssSyncExecuting,
|
|
||||||
isSmallScreen: dimensionsState.isSmallScreen
|
isSmallScreen: dimensionsState.isSmallScreen
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,9 +99,14 @@ class BookDetails extends Component {
|
|||||||
nextBook,
|
nextBook,
|
||||||
isSearching,
|
isSearching,
|
||||||
onRefreshPress,
|
onRefreshPress,
|
||||||
onSearchPress
|
onSearchPress,
|
||||||
|
statistics = {}
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
bookFileCount = 0
|
||||||
|
} = statistics;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOrganizeModalOpen,
|
isOrganizeModalOpen,
|
||||||
isRetagModalOpen,
|
isRetagModalOpen,
|
||||||
@@ -238,21 +243,21 @@ class BookDetails extends Component {
|
|||||||
className={styles.tab}
|
className={styles.tab}
|
||||||
selectedClassName={styles.selectedTab}
|
selectedClassName={styles.selectedTab}
|
||||||
>
|
>
|
||||||
History
|
{translate('History')}
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab
|
<Tab
|
||||||
className={styles.tab}
|
className={styles.tab}
|
||||||
selectedClassName={styles.selectedTab}
|
selectedClassName={styles.selectedTab}
|
||||||
>
|
>
|
||||||
Search
|
{translate('Search')}
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab
|
<Tab
|
||||||
className={styles.tab}
|
className={styles.tab}
|
||||||
selectedClassName={styles.selectedTab}
|
selectedClassName={styles.selectedTab}
|
||||||
>
|
>
|
||||||
Files
|
{translate('FilesTotal', [bookFileCount])}
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -335,6 +340,7 @@ BookDetails.propTypes = {
|
|||||||
ratings: PropTypes.object.isRequired,
|
ratings: PropTypes.object.isRequired,
|
||||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
links: PropTypes.arrayOf(PropTypes.object).isRequired,
|
links: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
statistics: PropTypes.object.isRequired,
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
shortDateFormat: PropTypes.string.isRequired,
|
||||||
isSaving: PropTypes.bool.isRequired,
|
isSaving: PropTypes.bool.isRequired,
|
||||||
|
|||||||
@@ -117,8 +117,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
font-weight: 300;
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
line-height: 50px;
|
line-height: 30px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,12 +21,7 @@ const defaultFontSize = parseInt(fonts.defaultFontSize);
|
|||||||
const lineHeight = parseFloat(fonts.lineHeight);
|
const lineHeight = parseFloat(fonts.lineHeight);
|
||||||
|
|
||||||
function getFanartUrl(images) {
|
function getFanartUrl(images) {
|
||||||
const fanartImage = images.find((x) => x.coverType === 'fanart');
|
return images.find((x) => x.coverType === 'fanart')?.url;
|
||||||
|
|
||||||
if (fanartImage) {
|
|
||||||
// Remove protocol
|
|
||||||
return fanartImage.url.replace(/^https?:/, '');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class BookDetailsHeader extends Component {
|
class BookDetailsHeader extends Component {
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import BookIndex from './BookIndex';
|
|||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createBookClientSideCollectionItemsSelector('bookIndex'),
|
createBookClientSideCollectionItemsSelector('bookIndex'),
|
||||||
createCommandExecutingSelector(commandNames.REFRESH_AUTHOR),
|
createCommandExecutingSelector(commandNames.BULK_REFRESH_AUTHOR),
|
||||||
createCommandExecutingSelector(commandNames.REFRESH_BOOK),
|
createCommandExecutingSelector(commandNames.BULK_REFRESH_BOOK),
|
||||||
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
||||||
createCommandExecutingSelector(commandNames.CUTOFF_UNMET_BOOK_SEARCH),
|
createCommandExecutingSelector(commandNames.CUTOFF_UNMET_BOOK_SEARCH),
|
||||||
createCommandExecutingSelector(commandNames.MISSING_BOOK_SEARCH),
|
createCommandExecutingSelector(commandNames.MISSING_BOOK_SEARCH),
|
||||||
|
|||||||
@@ -229,7 +229,6 @@ class BookIndexRow extends Component {
|
|||||||
className={styles[name]}
|
className={styles[name]}
|
||||||
>
|
>
|
||||||
{bookFileCount}
|
{bookFileCount}
|
||||||
|
|
||||||
</VirtualTableRowCell>
|
</VirtualTableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
.container {
|
||||||
|
border: 1px solid var(--borderColor);
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--inputBackgroundColor);
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// This file is automatically generated.
|
||||||
|
// Please do not change this file!
|
||||||
|
interface CssExports {
|
||||||
|
'container': string;
|
||||||
|
}
|
||||||
|
export const cssExports: CssExports;
|
||||||
|
export default cssExports;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import BookFileEditorTableContentConnector from './BookFileEditorTableContentConnector';
|
import BookFileEditorTableContentConnector from './BookFileEditorTableContentConnector';
|
||||||
|
import styles from './BookFileEditorTable.css';
|
||||||
|
|
||||||
function BookFileEditorTable(props) {
|
function BookFileEditorTable(props) {
|
||||||
const {
|
const {
|
||||||
@@ -7,9 +8,11 @@ function BookFileEditorTable(props) {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BookFileEditorTableContentConnector
|
<div className={styles.container}>
|
||||||
{...otherProps}
|
<BookFileEditorTableContentConnector
|
||||||
/>
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.filesTable {
|
.filesTable {
|
||||||
margin-bottom: 20px;
|
margin: 10px;
|
||||||
padding-top: 15px;
|
padding-top: 5px;
|
||||||
border: 1px solid var(--borderColor);
|
border: 1px solid var(--borderColor);
|
||||||
border-top: 1px solid var(--borderColor);
|
border-top: 1px solid var(--borderColor);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -13,9 +13,15 @@
|
|||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-right: auto;
|
margin: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectInput {
|
.selectInput {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blankpad {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
'actions': string;
|
'actions': string;
|
||||||
|
'blankpad': string;
|
||||||
'filesTable': string;
|
'filesTable': string;
|
||||||
'selectInput': string;
|
'selectInput': string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
import SelectInput from 'Components/Form/SelectInput';
|
import SelectInput from 'Components/Form/SelectInput';
|
||||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
@@ -120,7 +121,7 @@ class BookFileEditorTableContent extends Component {
|
|||||||
const hasSelectedFiles = this.getSelectedIds().length > 0;
|
const hasSelectedFiles = this.getSelectedIds().length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div>
|
||||||
{
|
{
|
||||||
isFetching && !isPopulated ?
|
isFetching && !isPopulated ?
|
||||||
<LoadingIndicator /> :
|
<LoadingIndicator /> :
|
||||||
@@ -129,13 +130,13 @@ class BookFileEditorTableContent extends Component {
|
|||||||
|
|
||||||
{
|
{
|
||||||
!isFetching && error ?
|
!isFetching && error ?
|
||||||
<div>{error}</div> :
|
<Alert kind={kinds.DANGER}>{error}</Alert> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
isPopulated && !items.length ?
|
isPopulated && !items.length ?
|
||||||
<div>
|
<div className={styles.blankpad}>
|
||||||
No book files to manage.
|
No book files to manage.
|
||||||
</div> :
|
</div> :
|
||||||
null
|
null
|
||||||
@@ -173,26 +174,30 @@ class BookFileEditorTableContent extends Component {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
<div className={styles.actions}>
|
{
|
||||||
<SpinnerButton
|
isPopulated && items.length ? (
|
||||||
kind={kinds.DANGER}
|
<div className={styles.actions}>
|
||||||
isSpinning={isDeleting}
|
<SpinnerButton
|
||||||
isDisabled={!hasSelectedFiles}
|
kind={kinds.DANGER}
|
||||||
onPress={this.onDeletePress}
|
isSpinning={isDeleting}
|
||||||
>
|
isDisabled={!hasSelectedFiles}
|
||||||
Delete
|
onPress={this.onDeletePress}
|
||||||
</SpinnerButton>
|
>
|
||||||
|
{translate('Delete')}
|
||||||
|
</SpinnerButton>
|
||||||
|
|
||||||
<div className={styles.selectInput}>
|
<div className={styles.selectInput}>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
name="quality"
|
name="quality"
|
||||||
value="selectQuality"
|
value="selectQuality"
|
||||||
values={qualityOptions}
|
values={qualityOptions}
|
||||||
isDisabled={!hasSelectedFiles}
|
isDisabled={!hasSelectedFiles}
|
||||||
onChange={this.onQualityChange}
|
onChange={this.onQualityChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
isOpen={isConfirmDeleteModalOpen}
|
isOpen={isConfirmDeleteModalOpen}
|
||||||
@@ -203,7 +208,7 @@ class BookFileEditorTableContent extends Component {
|
|||||||
onConfirm={this.onConfirmDelete}
|
onConfirm={this.onConfirmDelete}
|
||||||
onCancel={this.onConfirmDeleteModalClose}
|
onCancel={this.onConfirmDeleteModalClose}
|
||||||
/>
|
/>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class CalendarConnector extends Component {
|
|||||||
gotoCalendarToday
|
gotoCalendarToday
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
registerPagePopulator(this.repopulate);
|
registerPagePopulator(this.repopulate, ['bookFileUpdated', 'bookFileDeleted']);
|
||||||
|
|
||||||
if (useCurrentPage) {
|
if (useCurrentPage) {
|
||||||
fetchCalendar();
|
fetchCalendar();
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
.description {
|
|
||||||
line-height: $lineHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
line-height: $lineHeight;
|
||||||
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.version {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: $breakpointMedium) {
|
@media only screen and (max-width: $breakpointMedium) {
|
||||||
.image {
|
.image {
|
||||||
height: 250px;
|
height: 250px;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ interface CssExports {
|
|||||||
'image': string;
|
'image': string;
|
||||||
'imageContainer': string;
|
'imageContainer': string;
|
||||||
'message': string;
|
'message': string;
|
||||||
|
'version': string;
|
||||||
}
|
}
|
||||||
export const cssExports: CssExports;
|
export const cssExports: CssExports;
|
||||||
export default cssExports;
|
export default cssExports;
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import styles from './ErrorBoundaryError.css';
|
|
||||||
|
|
||||||
function ErrorBoundaryError(props) {
|
|
||||||
const {
|
|
||||||
className,
|
|
||||||
messageClassName,
|
|
||||||
detailsClassName,
|
|
||||||
message,
|
|
||||||
error,
|
|
||||||
info
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={className}>
|
|
||||||
<div className={messageClassName}>
|
|
||||||
{message}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.imageContainer}>
|
|
||||||
<img
|
|
||||||
className={styles.image}
|
|
||||||
src={`${window.Readarr.urlBase}/Content/Images/error.png`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<details className={detailsClassName}>
|
|
||||||
{
|
|
||||||
error &&
|
|
||||||
<div>
|
|
||||||
{error.toString()}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div className={styles.info}>
|
|
||||||
{info.componentStack}
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorBoundaryError.propTypes = {
|
|
||||||
className: PropTypes.string.isRequired,
|
|
||||||
messageClassName: PropTypes.string.isRequired,
|
|
||||||
detailsClassName: PropTypes.string.isRequired,
|
|
||||||
message: PropTypes.string.isRequired,
|
|
||||||
error: PropTypes.object.isRequired,
|
|
||||||
info: PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
ErrorBoundaryError.defaultProps = {
|
|
||||||
className: styles.container,
|
|
||||||
messageClassName: styles.message,
|
|
||||||
detailsClassName: styles.details,
|
|
||||||
message: 'There was an error loading this content'
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ErrorBoundaryError;
|
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import StackTrace from 'stacktrace-js';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import styles from './ErrorBoundaryError.css';
|
||||||
|
|
||||||
|
interface ErrorBoundaryErrorProps {
|
||||||
|
className: string;
|
||||||
|
messageClassName: string;
|
||||||
|
detailsClassName: string;
|
||||||
|
message: string;
|
||||||
|
error: Error;
|
||||||
|
info: {
|
||||||
|
componentStack: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function ErrorBoundaryError(props: ErrorBoundaryErrorProps) {
|
||||||
|
const {
|
||||||
|
className = styles.container,
|
||||||
|
messageClassName = styles.message,
|
||||||
|
detailsClassName = styles.details,
|
||||||
|
message = translate('ErrorLoadingContent'),
|
||||||
|
error,
|
||||||
|
info,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const [detailedError, setDetailedError] = useState<
|
||||||
|
StackTrace.StackFrame[] | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (error) {
|
||||||
|
StackTrace.fromError(error).then((de) => {
|
||||||
|
setDetailedError(de);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setDetailedError(null);
|
||||||
|
}
|
||||||
|
}, [error, setDetailedError]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
<div className={messageClassName}>{message}</div>
|
||||||
|
|
||||||
|
<div className={styles.imageContainer}>
|
||||||
|
<img
|
||||||
|
className={styles.image}
|
||||||
|
src={`${window.Readarr.urlBase}/Content/Images/error.png`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details className={detailsClassName}>
|
||||||
|
{error ? <div>{error.message}</div> : null}
|
||||||
|
|
||||||
|
{detailedError ? (
|
||||||
|
detailedError.map((d, index) => {
|
||||||
|
return (
|
||||||
|
<div key={index}>
|
||||||
|
{` at ${d.functionName} (${d.fileName}:${d.lineNumber}:${d.columnNumber})`}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<div>{info.componentStack}</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{
|
||||||
|
<div className={styles.version}>
|
||||||
|
Version: {window.Readarr.version}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ErrorBoundaryError;
|
||||||
@@ -9,6 +9,10 @@
|
|||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--inputHoverBackgroundColor);
|
background-color: var(--inputHoverBackgroundColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.isDisabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.optionCheck {
|
.optionCheck {
|
||||||
|
|||||||
@@ -202,6 +202,8 @@ class SignalRConnector extends Component {
|
|||||||
this.props.dispatchUpdateItem({ section, ...body.resource });
|
this.props.dispatchUpdateItem({ section, ...body.resource });
|
||||||
} else if (body.action === 'deleted') {
|
} else if (body.action === 'deleted') {
|
||||||
this.props.dispatchRemoveItem({ section, id: body.resource.id });
|
this.props.dispatchRemoveItem({ section, id: body.resource.id });
|
||||||
|
|
||||||
|
repopulatePage('bookFileDeleted');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repopulate the page to handle recently imported file
|
// Repopulate the page to handle recently imported file
|
||||||
|
|||||||
@@ -15,5 +15,5 @@
|
|||||||
"start_url": "../../../../",
|
"start_url": "../../../../",
|
||||||
"theme_color": "#3a3f51",
|
"theme_color": "#3a3f51",
|
||||||
"background_color": "#3a3f51",
|
"background_color": "#3a3f51",
|
||||||
"display": "standalone"
|
"display": "minimal-ui"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||||
|
|
||||||
|
// This file contains some helpers for power users in a browser console
|
||||||
|
|
||||||
|
let hasWarned = false;
|
||||||
|
|
||||||
|
function checkActivationWarning() {
|
||||||
|
if (!hasWarned) {
|
||||||
|
console.log('Activated ReadarrApi console helpers.');
|
||||||
|
console.warn('Be warned: There will be no further confirmation checks.');
|
||||||
|
hasWarned = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachAsyncActions(promise) {
|
||||||
|
promise.filter = function() {
|
||||||
|
const args = arguments;
|
||||||
|
const res = this.then((d) => d.filter(...args));
|
||||||
|
attachAsyncActions(res);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
promise.map = function() {
|
||||||
|
const args = arguments;
|
||||||
|
const res = this.then((d) => d.map(...args));
|
||||||
|
attachAsyncActions(res);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
promise.all = function() {
|
||||||
|
const res = this.then((d) => Promise.all(d));
|
||||||
|
attachAsyncActions(res);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
promise.forEach = function(action) {
|
||||||
|
const res = this.then((d) => Promise.all(d.map(action)));
|
||||||
|
attachAsyncActions(res);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResourceApi {
|
||||||
|
constructor(api, url) {
|
||||||
|
this.api = api;
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
single(id) {
|
||||||
|
return this.api.fetch(`${this.url}/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
all() {
|
||||||
|
return this.api.fetch(this.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
filter(pred) {
|
||||||
|
return this.all().filter(pred);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(resource) {
|
||||||
|
return this.api.fetch(`${this.url}/${resource.id}`, { method: 'PUT', data: resource });
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(resource) {
|
||||||
|
if (typeof resource === 'object' && resource !== null && resource.id) {
|
||||||
|
resource = resource.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resource || !Number.isInteger(resource)) {
|
||||||
|
throw Error('Invalid resource', resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.api.fetch(`${this.url}/${resource}`, { method: 'DELETE' });
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(url, options) {
|
||||||
|
return this.api.fetch(`${this.url}${url}`, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConsoleApi {
|
||||||
|
constructor() {
|
||||||
|
this.author = new ResourceApi(this, '/author');
|
||||||
|
}
|
||||||
|
|
||||||
|
resource(url) {
|
||||||
|
return new ResourceApi(this, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(url, options) {
|
||||||
|
checkActivationWarning();
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
const req = {
|
||||||
|
url,
|
||||||
|
method: options.method || 'GET'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options.data) {
|
||||||
|
req.dataType = 'json';
|
||||||
|
req.data = JSON.stringify(options.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
const promise = createAjaxRequest(req).request;
|
||||||
|
|
||||||
|
promise.fail((xhr) => {
|
||||||
|
console.error(`Failed to fetch ${url}`, xhr);
|
||||||
|
});
|
||||||
|
|
||||||
|
attachAsyncActions(promise);
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.ReadarrApi = new ConsoleApi();
|
||||||
|
|
||||||
|
export default ConsoleApi;
|
||||||
@@ -8,11 +8,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
flex: 1 0 300px;
|
@add-mixin truncate;
|
||||||
|
|
||||||
|
flex: 0 1 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.foreignId {
|
.foreignId {
|
||||||
flex: 0 0 200px;
|
flex: 0 0 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.foreignId {
|
.name {
|
||||||
flex: 0 0 200px;
|
flex: 0 1 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.foreignId {
|
||||||
flex: 1 0 300px;
|
flex: 0 0 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.addImportListExclusion {
|
.addImportListExclusion {
|
||||||
|
|||||||
@@ -212,26 +212,24 @@ class MediaManagement extends Component {
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
{
|
{
|
||||||
settings.importExtraFiles.value &&
|
settings.importExtraFiles.value ?
|
||||||
<FormGroup
|
<FormGroup
|
||||||
advancedSettings={advancedSettings}
|
advancedSettings={advancedSettings}
|
||||||
isAdvanced={true}
|
isAdvanced={true}
|
||||||
>
|
>
|
||||||
<FormLabel>
|
<FormLabel>{translate('ImportExtraFiles')}</FormLabel>
|
||||||
{translate('ImportExtraFiles')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.TEXT}
|
type={inputTypes.TEXT}
|
||||||
name="extraFileExtensions"
|
name="extraFileExtensions"
|
||||||
helpTexts={[
|
helpTexts={[
|
||||||
translate('ExtraFileExtensionsHelpTexts1'),
|
translate('ExtraFileExtensionsHelpText'),
|
||||||
translate('ExtraFileExtensionsHelpTexts2')
|
translate('ExtraFileExtensionsHelpTextsExamples')
|
||||||
]}
|
]}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...settings.extraFileExtensions}
|
{...settings.extraFileExtensions}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup> : null
|
||||||
}
|
}
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class Notification extends Component {
|
|||||||
onReleaseImport,
|
onReleaseImport,
|
||||||
onUpgrade,
|
onUpgrade,
|
||||||
onRename,
|
onRename,
|
||||||
|
onAuthorAdded,
|
||||||
onAuthorDelete,
|
onAuthorDelete,
|
||||||
onBookDelete,
|
onBookDelete,
|
||||||
onBookFileDelete,
|
onBookFileDelete,
|
||||||
@@ -73,6 +74,7 @@ class Notification extends Component {
|
|||||||
supportsOnReleaseImport,
|
supportsOnReleaseImport,
|
||||||
supportsOnUpgrade,
|
supportsOnUpgrade,
|
||||||
supportsOnRename,
|
supportsOnRename,
|
||||||
|
supportsOnAuthorAdded,
|
||||||
supportsOnAuthorDelete,
|
supportsOnAuthorDelete,
|
||||||
supportsOnBookDelete,
|
supportsOnBookDelete,
|
||||||
supportsOnBookFileDelete,
|
supportsOnBookFileDelete,
|
||||||
@@ -136,6 +138,14 @@ class Notification extends Component {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
supportsOnAuthorAdded && onAuthorAdded ?
|
||||||
|
<Label kind={kinds.SUCCESS}>
|
||||||
|
{translate('OnAuthorAdded')}
|
||||||
|
</Label> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
supportsOnAuthorDelete && onAuthorDelete ?
|
supportsOnAuthorDelete && onAuthorDelete ?
|
||||||
<Label kind={kinds.SUCCESS}>
|
<Label kind={kinds.SUCCESS}>
|
||||||
@@ -244,6 +254,7 @@ Notification.propTypes = {
|
|||||||
onReleaseImport: PropTypes.bool.isRequired,
|
onReleaseImport: PropTypes.bool.isRequired,
|
||||||
onUpgrade: PropTypes.bool.isRequired,
|
onUpgrade: PropTypes.bool.isRequired,
|
||||||
onRename: PropTypes.bool.isRequired,
|
onRename: PropTypes.bool.isRequired,
|
||||||
|
onAuthorAdded: PropTypes.bool.isRequired,
|
||||||
onAuthorDelete: PropTypes.bool.isRequired,
|
onAuthorDelete: PropTypes.bool.isRequired,
|
||||||
onBookDelete: PropTypes.bool.isRequired,
|
onBookDelete: PropTypes.bool.isRequired,
|
||||||
onBookFileDelete: PropTypes.bool.isRequired,
|
onBookFileDelete: PropTypes.bool.isRequired,
|
||||||
@@ -257,6 +268,7 @@ Notification.propTypes = {
|
|||||||
supportsOnReleaseImport: PropTypes.bool.isRequired,
|
supportsOnReleaseImport: PropTypes.bool.isRequired,
|
||||||
supportsOnUpgrade: PropTypes.bool.isRequired,
|
supportsOnUpgrade: PropTypes.bool.isRequired,
|
||||||
supportsOnRename: PropTypes.bool.isRequired,
|
supportsOnRename: PropTypes.bool.isRequired,
|
||||||
|
supportsOnAuthorAdded: PropTypes.bool.isRequired,
|
||||||
supportsOnAuthorDelete: PropTypes.bool.isRequired,
|
supportsOnAuthorDelete: PropTypes.bool.isRequired,
|
||||||
supportsOnBookDelete: PropTypes.bool.isRequired,
|
supportsOnBookDelete: PropTypes.bool.isRequired,
|
||||||
supportsOnBookFileDelete: PropTypes.bool.isRequired,
|
supportsOnBookFileDelete: PropTypes.bool.isRequired,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ function NotificationEventItems(props) {
|
|||||||
onReleaseImport,
|
onReleaseImport,
|
||||||
onUpgrade,
|
onUpgrade,
|
||||||
onRename,
|
onRename,
|
||||||
|
onAuthorAdded,
|
||||||
onAuthorDelete,
|
onAuthorDelete,
|
||||||
onBookDelete,
|
onBookDelete,
|
||||||
onBookFileDelete,
|
onBookFileDelete,
|
||||||
@@ -32,6 +33,7 @@ function NotificationEventItems(props) {
|
|||||||
supportsOnReleaseImport,
|
supportsOnReleaseImport,
|
||||||
supportsOnUpgrade,
|
supportsOnUpgrade,
|
||||||
supportsOnRename,
|
supportsOnRename,
|
||||||
|
supportsOnAuthorAdded,
|
||||||
supportsOnAuthorDelete,
|
supportsOnAuthorDelete,
|
||||||
supportsOnBookDelete,
|
supportsOnBookDelete,
|
||||||
supportsOnBookFileDelete,
|
supportsOnBookFileDelete,
|
||||||
@@ -123,6 +125,17 @@ function NotificationEventItems(props) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.CHECK}
|
||||||
|
name="onAuthorAdded"
|
||||||
|
helpText={translate('OnAuthorAddedHelpText')}
|
||||||
|
isDisabled={!supportsOnAuthorAdded.value}
|
||||||
|
{...onAuthorAdded}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ export default {
|
|||||||
selectedSchema.onReleaseImport = selectedSchema.supportsOnReleaseImport;
|
selectedSchema.onReleaseImport = selectedSchema.supportsOnReleaseImport;
|
||||||
selectedSchema.onUpgrade = selectedSchema.supportsOnUpgrade;
|
selectedSchema.onUpgrade = selectedSchema.supportsOnUpgrade;
|
||||||
selectedSchema.onRename = selectedSchema.supportsOnRename;
|
selectedSchema.onRename = selectedSchema.supportsOnRename;
|
||||||
|
selectedSchema.onAuthorAdded = selectedSchema.supportsOnAuthorAdded;
|
||||||
selectedSchema.onAuthorDelete = selectedSchema.supportsOnAuthorDelete;
|
selectedSchema.onAuthorDelete = selectedSchema.supportsOnAuthorDelete;
|
||||||
selectedSchema.onBookDelete = selectedSchema.supportsOnBookDelete;
|
selectedSchema.onBookDelete = selectedSchema.supportsOnBookDelete;
|
||||||
selectedSchema.onBookFileDelete = selectedSchema.supportsOnBookFileDelete;
|
selectedSchema.onBookFileDelete = selectedSchema.supportsOnBookFileDelete;
|
||||||
|
|||||||
@@ -41,6 +41,14 @@ export const defaultState = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
columns: [
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'select',
|
||||||
|
columnLabel: 'Select',
|
||||||
|
isSortable: false,
|
||||||
|
isVisible: true,
|
||||||
|
isModifiable: false,
|
||||||
|
isHidden: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'path',
|
name: 'path',
|
||||||
label: 'Path',
|
label: 'Path',
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export const defaultState = {
|
|||||||
bookFileCount: function(item) {
|
bookFileCount: function(item) {
|
||||||
const { statistics = {} } = item;
|
const { statistics = {} } = item;
|
||||||
|
|
||||||
return statistics.bookCount || 0;
|
return statistics.bookFileCount || 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
ratings: function(item) {
|
ratings: function(item) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { sortDirections } from 'Helpers/Props';
|
|||||||
import { createThunk, handleThunks } from 'Store/thunks';
|
import { createThunk, handleThunks } from 'Store/thunks';
|
||||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||||
import updateSectionState from 'Utilities/State/updateSectionState';
|
import updateSectionState from 'Utilities/State/updateSectionState';
|
||||||
|
import naturalExpansion from 'Utilities/String/naturalExpansion';
|
||||||
import { set, update, updateItem } from './baseActions';
|
import { set, update, updateItem } from './baseActions';
|
||||||
import createFetchHandler from './Creators/createFetchHandler';
|
import createFetchHandler from './Creators/createFetchHandler';
|
||||||
import createHandleActions from './Creators/createHandleActions';
|
import createHandleActions from './Creators/createHandleActions';
|
||||||
@@ -17,6 +18,7 @@ export const section = 'interactiveImport';
|
|||||||
|
|
||||||
const booksSection = `${section}.books`;
|
const booksSection = `${section}.books`;
|
||||||
const bookFilesSection = `${section}.bookFiles`;
|
const bookFilesSection = `${section}.bookFiles`;
|
||||||
|
let abortCurrentFetchRequest = null;
|
||||||
let abortCurrentRequest = null;
|
let abortCurrentRequest = null;
|
||||||
let currentIds = [];
|
let currentIds = [];
|
||||||
|
|
||||||
@@ -32,15 +34,17 @@ export const defaultState = {
|
|||||||
error: null,
|
error: null,
|
||||||
items: [],
|
items: [],
|
||||||
pendingChanges: {},
|
pendingChanges: {},
|
||||||
sortKey: 'quality',
|
sortKey: 'path',
|
||||||
sortDirection: sortDirections.DESCENDING,
|
sortDirection: sortDirections.ASCENDING,
|
||||||
|
secondarySortKey: 'path',
|
||||||
|
secondarySortDirection: sortDirections.ASCENDING,
|
||||||
recentFolders: [],
|
recentFolders: [],
|
||||||
importMode: 'chooseImportMode',
|
importMode: 'chooseImportMode',
|
||||||
sortPredicates: {
|
sortPredicates: {
|
||||||
path: function(item, direction) {
|
path: function(item, direction) {
|
||||||
const path = item.path;
|
const path = item.path;
|
||||||
|
|
||||||
return path.toLowerCase();
|
return naturalExpansion(path.toLowerCase());
|
||||||
},
|
},
|
||||||
|
|
||||||
author: function(item, direction) {
|
author: function(item, direction) {
|
||||||
@@ -74,6 +78,8 @@ export const defaultState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const persistState = [
|
export const persistState = [
|
||||||
|
'interactiveImport.sortKey',
|
||||||
|
'interactiveImport.sortDirection',
|
||||||
'interactiveImport.recentFolders',
|
'interactiveImport.recentFolders',
|
||||||
'interactiveImport.importMode'
|
'interactiveImport.importMode'
|
||||||
];
|
];
|
||||||
@@ -122,6 +128,11 @@ export const clearInteractiveImportBookFiles = createAction(CLEAR_INTERACTIVE_IM
|
|||||||
// Action Handlers
|
// Action Handlers
|
||||||
export const actionHandlers = handleThunks({
|
export const actionHandlers = handleThunks({
|
||||||
[FETCH_INTERACTIVE_IMPORT_ITEMS]: function(getState, payload, dispatch) {
|
[FETCH_INTERACTIVE_IMPORT_ITEMS]: function(getState, payload, dispatch) {
|
||||||
|
if (abortCurrentFetchRequest) {
|
||||||
|
abortCurrentFetchRequest();
|
||||||
|
abortCurrentFetchRequest = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!payload.downloadId && !payload.folder) {
|
if (!payload.downloadId && !payload.folder) {
|
||||||
dispatch(set({ section, error: { message: '`downloadId` or `folder` is required.' } }));
|
dispatch(set({ section, error: { message: '`downloadId` or `folder` is required.' } }));
|
||||||
return;
|
return;
|
||||||
@@ -129,12 +140,14 @@ export const actionHandlers = handleThunks({
|
|||||||
|
|
||||||
dispatch(set({ section, isFetching: true }));
|
dispatch(set({ section, isFetching: true }));
|
||||||
|
|
||||||
const promise = createAjaxRequest({
|
const { request, abortRequest } = createAjaxRequest({
|
||||||
url: '/manualimport',
|
url: '/manualimport',
|
||||||
data: payload
|
data: payload
|
||||||
}).request;
|
});
|
||||||
|
|
||||||
promise.done((data) => {
|
abortCurrentFetchRequest = abortRequest;
|
||||||
|
|
||||||
|
request.done((data) => {
|
||||||
dispatch(batchActions([
|
dispatch(batchActions([
|
||||||
update({ section, data }),
|
update({ section, data }),
|
||||||
|
|
||||||
@@ -147,7 +160,11 @@ export const actionHandlers = handleThunks({
|
|||||||
]));
|
]));
|
||||||
});
|
});
|
||||||
|
|
||||||
promise.fail((xhr) => {
|
request.fail((xhr) => {
|
||||||
|
if (xhr.aborted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(set({
|
dispatch(set({
|
||||||
section,
|
section,
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ function getInternalLink(source) {
|
|||||||
function getTestLink(source, props) {
|
function getTestLink(source, props) {
|
||||||
switch (source) {
|
switch (source) {
|
||||||
case 'IndexerStatusCheck':
|
case 'IndexerStatusCheck':
|
||||||
|
case 'IndexerLongTermStatusCheck':
|
||||||
return (
|
return (
|
||||||
<SpinnerIconButton
|
<SpinnerIconButton
|
||||||
name={icons.TEST}
|
name={icons.TEST}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
import PageContent from 'Components/Page/PageContent';
|
import PageContent from 'Components/Page/PageContent';
|
||||||
import PageContentBody from 'Components/Page/PageContentBody';
|
import PageContentBody from 'Components/Page/PageContentBody';
|
||||||
@@ -9,8 +10,12 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
|||||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||||
import VirtualTable from 'Components/Table/VirtualTable';
|
import VirtualTable from 'Components/Table/VirtualTable';
|
||||||
import VirtualTableRow from 'Components/Table/VirtualTableRow';
|
import VirtualTableRow from 'Components/Table/VirtualTableRow';
|
||||||
import { align, icons, sortDirections } from 'Helpers/Props';
|
import { align, icons, kinds, sortDirections } from 'Helpers/Props';
|
||||||
|
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||||
|
import selectAll from 'Utilities/Table/selectAll';
|
||||||
|
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||||
import UnmappedFilesTableHeader from './UnmappedFilesTableHeader';
|
import UnmappedFilesTableHeader from './UnmappedFilesTableHeader';
|
||||||
import UnmappedFilesTableRow from './UnmappedFilesTableRow';
|
import UnmappedFilesTableRow from './UnmappedFilesTableRow';
|
||||||
|
|
||||||
@@ -23,10 +28,43 @@ class UnmappedFilesTable extends Component {
|
|||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
scroller: null
|
scroller: null,
|
||||||
|
allSelected: false,
|
||||||
|
allUnselected: false,
|
||||||
|
lastToggled: null,
|
||||||
|
selectedState: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.setSelectedState();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
const {
|
||||||
|
items,
|
||||||
|
sortKey,
|
||||||
|
sortDirection,
|
||||||
|
isDeleting,
|
||||||
|
deleteError
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (sortKey !== prevProps.sortKey ||
|
||||||
|
sortDirection !== prevProps.sortDirection ||
|
||||||
|
hasDifferentItemsOrOrder(prevProps.items, items)
|
||||||
|
) {
|
||||||
|
this.setSelectedState();
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasFinishedDeleting = prevProps.isDeleting &&
|
||||||
|
!isDeleting &&
|
||||||
|
!deleteError;
|
||||||
|
|
||||||
|
if (hasFinishedDeleting) {
|
||||||
|
this.onSelectAllChange({ value: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Control
|
// Control
|
||||||
|
|
||||||
@@ -34,6 +72,68 @@ class UnmappedFilesTable extends Component {
|
|||||||
this.setState({ scroller: ref });
|
this.setState({ scroller: ref });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getSelectedIds = () => {
|
||||||
|
if (this.state.allUnselected) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return getSelectedIds(this.state.selectedState);
|
||||||
|
};
|
||||||
|
|
||||||
|
setSelectedState() {
|
||||||
|
const {
|
||||||
|
items
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
selectedState
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
const newSelectedState = {};
|
||||||
|
|
||||||
|
items.forEach((file) => {
|
||||||
|
const isItemSelected = selectedState[file.id];
|
||||||
|
|
||||||
|
if (isItemSelected) {
|
||||||
|
newSelectedState[file.id] = isItemSelected;
|
||||||
|
} else {
|
||||||
|
newSelectedState[file.id] = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedCount = getSelectedIds(newSelectedState).length;
|
||||||
|
const newStateCount = Object.keys(newSelectedState).length;
|
||||||
|
let isAllSelected = false;
|
||||||
|
let isAllUnselected = false;
|
||||||
|
|
||||||
|
if (selectedCount === 0) {
|
||||||
|
isAllUnselected = true;
|
||||||
|
} else if (selectedCount === newStateCount) {
|
||||||
|
isAllSelected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ selectedState: newSelectedState, allSelected: isAllSelected, allUnselected: isAllUnselected });
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectAllChange = ({ value }) => {
|
||||||
|
this.setState(selectAll(this.state.selectedState, value));
|
||||||
|
};
|
||||||
|
|
||||||
|
onSelectAllPress = () => {
|
||||||
|
this.onSelectAllChange({ value: !this.state.allSelected });
|
||||||
|
};
|
||||||
|
|
||||||
|
onSelectedChange = ({ id, value, shiftKey = false }) => {
|
||||||
|
this.setState((state) => {
|
||||||
|
return toggleSelected(state, this.props.items, id, value, shiftKey);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onDeleteUnmappedFilesPress = () => {
|
||||||
|
const selectedIds = this.getSelectedIds();
|
||||||
|
|
||||||
|
this.props.deleteUnmappedFiles(selectedIds);
|
||||||
|
};
|
||||||
|
|
||||||
rowRenderer = ({ key, rowIndex, style }) => {
|
rowRenderer = ({ key, rowIndex, style }) => {
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
@@ -41,6 +141,10 @@ class UnmappedFilesTable extends Component {
|
|||||||
deleteUnmappedFile
|
deleteUnmappedFile
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
selectedState
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
const item = items[rowIndex];
|
const item = items[rowIndex];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -51,6 +155,8 @@ class UnmappedFilesTable extends Component {
|
|||||||
<UnmappedFilesTableRow
|
<UnmappedFilesTableRow
|
||||||
key={item.id}
|
key={item.id}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
isSelected={selectedState[item.id]}
|
||||||
|
onSelectedChange={this.onSelectedChange}
|
||||||
deleteUnmappedFile={deleteUnmappedFile}
|
deleteUnmappedFile={deleteUnmappedFile}
|
||||||
{...item}
|
{...item}
|
||||||
/>
|
/>
|
||||||
@@ -63,6 +169,7 @@ class UnmappedFilesTable extends Component {
|
|||||||
const {
|
const {
|
||||||
isFetching,
|
isFetching,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
|
isDeleting,
|
||||||
error,
|
error,
|
||||||
items,
|
items,
|
||||||
columns,
|
columns,
|
||||||
@@ -72,13 +179,19 @@ class UnmappedFilesTable extends Component {
|
|||||||
onSortPress,
|
onSortPress,
|
||||||
isScanningFolders,
|
isScanningFolders,
|
||||||
onAddMissingAuthorsPress,
|
onAddMissingAuthorsPress,
|
||||||
|
deleteUnmappedFiles,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
scroller
|
scroller,
|
||||||
|
allSelected,
|
||||||
|
allUnselected,
|
||||||
|
selectedState
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
|
const selectedTrackFileIds = this.getSelectedIds();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContent title={translate('UnmappedFiles')}>
|
<PageContent title={translate('UnmappedFiles')}>
|
||||||
<PageToolbar>
|
<PageToolbar>
|
||||||
@@ -90,6 +203,13 @@ class UnmappedFilesTable extends Component {
|
|||||||
isSpinning={isScanningFolders}
|
isSpinning={isScanningFolders}
|
||||||
onPress={onAddMissingAuthorsPress}
|
onPress={onAddMissingAuthorsPress}
|
||||||
/>
|
/>
|
||||||
|
<PageToolbarButton
|
||||||
|
label={translate('DeleteSelected')}
|
||||||
|
iconName={icons.DELETE}
|
||||||
|
isDisabled={selectedTrackFileIds.length === 0}
|
||||||
|
isSpinning={isDeleting}
|
||||||
|
onPress={this.onDeleteUnmappedFilesPress}
|
||||||
|
/>
|
||||||
</PageToolbarSection>
|
</PageToolbarSection>
|
||||||
|
|
||||||
<PageToolbarSection alignContent={align.RIGHT}>
|
<PageToolbarSection alignContent={align.RIGHT}>
|
||||||
@@ -117,9 +237,9 @@ class UnmappedFilesTable extends Component {
|
|||||||
|
|
||||||
{
|
{
|
||||||
isPopulated && !error && !items.length &&
|
isPopulated && !error && !items.length &&
|
||||||
<div>
|
<Alert kind={kinds.INFO}>
|
||||||
Success! My work is done, all files on disk are matched to known books.
|
Success! My work is done, all files on disk are matched to known books.
|
||||||
</div>
|
</Alert>
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -138,8 +258,12 @@ class UnmappedFilesTable extends Component {
|
|||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
onTableOptionChange={onTableOptionChange}
|
onTableOptionChange={onTableOptionChange}
|
||||||
onSortPress={onSortPress}
|
onSortPress={onSortPress}
|
||||||
|
allSelected={allSelected}
|
||||||
|
allUnselected={allUnselected}
|
||||||
|
onSelectAllChange={this.onSelectAllChange}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
selectedState={selectedState}
|
||||||
sortKey={sortKey}
|
sortKey={sortKey}
|
||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
/>
|
/>
|
||||||
@@ -153,6 +277,8 @@ class UnmappedFilesTable extends Component {
|
|||||||
UnmappedFilesTable.propTypes = {
|
UnmappedFilesTable.propTypes = {
|
||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
|
isDeleting: PropTypes.bool.isRequired,
|
||||||
|
deleteError: PropTypes.object,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
@@ -161,6 +287,7 @@ UnmappedFilesTable.propTypes = {
|
|||||||
onTableOptionChange: PropTypes.func.isRequired,
|
onTableOptionChange: PropTypes.func.isRequired,
|
||||||
onSortPress: PropTypes.func.isRequired,
|
onSortPress: PropTypes.func.isRequired,
|
||||||
deleteUnmappedFile: PropTypes.func.isRequired,
|
deleteUnmappedFile: PropTypes.func.isRequired,
|
||||||
|
deleteUnmappedFiles: PropTypes.func.isRequired,
|
||||||
isScanningFolders: PropTypes.bool.isRequired,
|
isScanningFolders: PropTypes.bool.isRequired,
|
||||||
onAddMissingAuthorsPress: PropTypes.func.isRequired
|
onAddMissingAuthorsPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { connect } from 'react-redux';
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import * as commandNames from 'Commands/commandNames';
|
import * as commandNames from 'Commands/commandNames';
|
||||||
import withCurrentPage from 'Components/withCurrentPage';
|
import withCurrentPage from 'Components/withCurrentPage';
|
||||||
import { deleteBookFile, fetchBookFiles, setBookFilesSort, setBookFilesTableOption } from 'Store/Actions/bookFileActions';
|
import { deleteBookFile, deleteBookFiles, fetchBookFiles, setBookFilesSort, setBookFilesTableOption } from 'Store/Actions/bookFileActions';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||||
@@ -28,7 +28,9 @@ function createMapStateToProps() {
|
|||||||
items,
|
items,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = bookFiles;
|
} = bookFiles;
|
||||||
|
|
||||||
const unmappedFiles = _.filter(items, { bookId: 0 });
|
const unmappedFiles = _.filter(items, { bookId: 0 });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: unmappedFiles,
|
items: unmappedFiles,
|
||||||
...otherProps,
|
...otherProps,
|
||||||
@@ -57,6 +59,10 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
dispatch(deleteBookFile({ id }));
|
dispatch(deleteBookFile({ id }));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
deleteUnmappedFiles(bookFileIds) {
|
||||||
|
dispatch(deleteBookFiles({ bookFileIds }));
|
||||||
|
},
|
||||||
|
|
||||||
onAddMissingAuthorsPress() {
|
onAddMissingAuthorsPress() {
|
||||||
dispatch(executeCommand({
|
dispatch(executeCommand({
|
||||||
name: commandNames.RESCAN_FOLDERS,
|
name: commandNames.RESCAN_FOLDERS,
|
||||||
@@ -106,7 +112,8 @@ UnmappedFilesTableConnector.propTypes = {
|
|||||||
onSortPress: PropTypes.func.isRequired,
|
onSortPress: PropTypes.func.isRequired,
|
||||||
onTableOptionChange: PropTypes.func.isRequired,
|
onTableOptionChange: PropTypes.func.isRequired,
|
||||||
fetchUnmappedFiles: PropTypes.func.isRequired,
|
fetchUnmappedFiles: PropTypes.func.isRequired,
|
||||||
deleteUnmappedFile: PropTypes.func.isRequired
|
deleteUnmappedFile: PropTypes.func.isRequired,
|
||||||
|
deleteUnmappedFiles: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withCurrentPage(
|
export default withCurrentPage(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import IconButton from 'Components/Link/IconButton';
|
|||||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||||
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
|
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
|
||||||
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
|
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
|
||||||
|
import VirtualTableSelectAllHeaderCell from 'Components/Table/VirtualTableSelectAllHeaderCell';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
// import hasGrowableColumns from './hasGrowableColumns';
|
// import hasGrowableColumns from './hasGrowableColumns';
|
||||||
import styles from './UnmappedFilesTableHeader.css';
|
import styles from './UnmappedFilesTableHeader.css';
|
||||||
@@ -12,6 +13,9 @@ function UnmappedFilesTableHeader(props) {
|
|||||||
const {
|
const {
|
||||||
columns,
|
columns,
|
||||||
onTableOptionChange,
|
onTableOptionChange,
|
||||||
|
allSelected,
|
||||||
|
allUnselected,
|
||||||
|
onSelectAllChange,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -30,6 +34,17 @@ function UnmappedFilesTableHeader(props) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === 'select') {
|
||||||
|
return (
|
||||||
|
<VirtualTableSelectAllHeaderCell
|
||||||
|
key={name}
|
||||||
|
allSelected={allSelected}
|
||||||
|
allUnselected={allUnselected}
|
||||||
|
onSelectAllChange={onSelectAllChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'actions') {
|
if (name === 'actions') {
|
||||||
return (
|
return (
|
||||||
<VirtualTableHeaderCell
|
<VirtualTableHeaderCell
|
||||||
@@ -71,6 +86,9 @@ function UnmappedFilesTableHeader(props) {
|
|||||||
|
|
||||||
UnmappedFilesTableHeader.propTypes = {
|
UnmappedFilesTableHeader.propTypes = {
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
allSelected: PropTypes.bool.isRequired,
|
||||||
|
allUnselected: PropTypes.bool.isRequired,
|
||||||
|
onSelectAllChange: PropTypes.func.isRequired,
|
||||||
onTableOptionChange: PropTypes.func.isRequired
|
onTableOptionChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,3 +20,9 @@
|
|||||||
|
|
||||||
flex: 0 0 100px;
|
flex: 0 0 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.checkInput {
|
||||||
|
composes: input from '~Components/Form/CheckInput.css';
|
||||||
|
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
'actions': string;
|
'actions': string;
|
||||||
|
'checkInput': string;
|
||||||
'dateAdded': string;
|
'dateAdded': string;
|
||||||
'path': string;
|
'path': string;
|
||||||
'quality': string;
|
'quality': string;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import IconButton from 'Components/Link/IconButton';
|
|||||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||||
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
||||||
|
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
@@ -69,7 +70,9 @@ class UnmappedFilesTableRow extends Component {
|
|||||||
size,
|
size,
|
||||||
dateAdded,
|
dateAdded,
|
||||||
quality,
|
quality,
|
||||||
columns
|
columns,
|
||||||
|
isSelected,
|
||||||
|
onSelectedChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const folder = path.substring(0, Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')));
|
const folder = path.substring(0, Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')));
|
||||||
@@ -93,6 +96,19 @@ class UnmappedFilesTableRow extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === 'select') {
|
||||||
|
return (
|
||||||
|
<VirtualTableSelectCell
|
||||||
|
inputClassName={styles.checkInput}
|
||||||
|
id={id}
|
||||||
|
key={name}
|
||||||
|
isSelected={isSelected}
|
||||||
|
isDisabled={false}
|
||||||
|
onSelectedChange={onSelectedChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'path') {
|
if (name === 'path') {
|
||||||
return (
|
return (
|
||||||
<VirtualTableRowCell
|
<VirtualTableRowCell
|
||||||
@@ -208,6 +224,8 @@ UnmappedFilesTableRow.propTypes = {
|
|||||||
quality: PropTypes.object.isRequired,
|
quality: PropTypes.object.isRequired,
|
||||||
dateAdded: PropTypes.string.isRequired,
|
dateAdded: PropTypes.string.isRequired,
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
isSelected: PropTypes.bool,
|
||||||
|
onSelectedChange: PropTypes.func.isRequired,
|
||||||
deleteUnmappedFile: PropTypes.func.isRequired
|
deleteUnmappedFile: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
const regex = /\d+/g;
|
||||||
|
|
||||||
|
function naturalExpansion(input) {
|
||||||
|
if (!input) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return input.replace(regex, (n) => n.padStart(8, '0'));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default naturalExpansion;
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
|
const regex = /\b\w+/g;
|
||||||
|
|
||||||
function titleCase(input) {
|
function titleCase(input) {
|
||||||
if (!input) {
|
if (!input) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return input.replace(/\b\w+/g, (match) => {
|
return input.replace(regex, (match) => {
|
||||||
return match.charAt(0).toUpperCase() + match.substr(1).toLowerCase();
|
return match.charAt(0).toUpperCase() + match.substr(1).toLowerCase();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export async function fetchTranslations(): Promise<boolean> {
|
|||||||
|
|
||||||
export default function translate(
|
export default function translate(
|
||||||
key: string,
|
key: string,
|
||||||
tokens?: Record<string, string | number | boolean>
|
tokens: Record<string, string | number | boolean> = { appName: 'Readarr' }
|
||||||
) {
|
) {
|
||||||
const translation = translations[key] || key;
|
const translation = translations[key] || key;
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class CutoffUnmetConnector extends Component {
|
|||||||
gotoCutoffUnmetFirstPage
|
gotoCutoffUnmetFirstPage
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
registerPagePopulator(this.repopulate, ['bookFileUpdated']);
|
registerPagePopulator(this.repopulate, ['bookFileUpdated', 'bookFileDeleted']);
|
||||||
|
|
||||||
if (useCurrentPage) {
|
if (useCurrentPage) {
|
||||||
fetchCutoffUnmet();
|
fetchCutoffUnmet();
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class MissingConnector extends Component {
|
|||||||
gotoMissingFirstPage
|
gotoMissingFirstPage
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
registerPagePopulator(this.repopulate, ['bookFileUpdated']);
|
registerPagePopulator(this.repopulate, ['bookFileUpdated', 'bookFileDeleted']);
|
||||||
|
|
||||||
if (useCurrentPage) {
|
if (useCurrentPage) {
|
||||||
fetchMissing();
|
fetchMissing();
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { render } from 'react-dom';
|
|||||||
import createAppStore from 'Store/createAppStore';
|
import createAppStore from 'Store/createAppStore';
|
||||||
import App from './App/App';
|
import App from './App/App';
|
||||||
|
|
||||||
|
import 'Diag/ConsoleApi';
|
||||||
|
|
||||||
export async function bootstrap() {
|
export async function bootstrap() {
|
||||||
const history = createBrowserHistory();
|
const history = createBrowserHistory();
|
||||||
const store = createAppStore(history);
|
const store = createAppStore(history);
|
||||||
|
|||||||
+18
-20
@@ -30,7 +30,7 @@
|
|||||||
"@fortawesome/free-regular-svg-icons": "6.4.0",
|
"@fortawesome/free-regular-svg-icons": "6.4.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "6.4.0",
|
"@fortawesome/free-solid-svg-icons": "6.4.0",
|
||||||
"@fortawesome/react-fontawesome": "0.2.0",
|
"@fortawesome/react-fontawesome": "0.2.0",
|
||||||
"@microsoft/signalr": "6.0.16",
|
"@microsoft/signalr": "6.0.25",
|
||||||
"@sentry/browser": "7.51.2",
|
"@sentry/browser": "7.51.2",
|
||||||
"@sentry/integrations": "7.51.2",
|
"@sentry/integrations": "7.51.2",
|
||||||
"@types/node": "18.16.16",
|
"@types/node": "18.16.16",
|
||||||
@@ -83,30 +83,28 @@
|
|||||||
"redux-localstorage": "0.4.1",
|
"redux-localstorage": "0.4.1",
|
||||||
"redux-thunk": "2.3.0",
|
"redux-thunk": "2.3.0",
|
||||||
"reselect": "4.1.8",
|
"reselect": "4.1.8",
|
||||||
|
"stacktrace-js": "2.0.2",
|
||||||
"typescript": "4.9.5"
|
"typescript": "4.9.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.22.9",
|
"@babel/core": "7.22.11",
|
||||||
"@babel/eslint-parser": "7.22.9",
|
"@babel/eslint-parser": "7.22.11",
|
||||||
"@babel/plugin-proposal-class-properties": "7.18.6",
|
|
||||||
"@babel/plugin-proposal-export-default-from": "7.22.5",
|
"@babel/plugin-proposal-export-default-from": "7.22.5",
|
||||||
"@babel/plugin-proposal-export-namespace-from": "7.18.9",
|
|
||||||
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
|
|
||||||
"@babel/plugin-proposal-optional-chaining": "7.21.0",
|
|
||||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||||
"@babel/preset-env": "7.22.9",
|
"@babel/preset-env": "7.22.15",
|
||||||
"@babel/preset-react": "7.22.5",
|
"@babel/preset-react": "7.22.5",
|
||||||
"@babel/preset-typescript": "7.22.5",
|
"@babel/preset-typescript": "7.22.11",
|
||||||
"@types/lodash": "4.14.197",
|
"@types/lodash": "4.14.197",
|
||||||
|
"@types/react-lazyload": "3.2.1",
|
||||||
"@types/redux-actions": "2.6.2",
|
"@types/redux-actions": "2.6.2",
|
||||||
"@typescript-eslint/eslint-plugin": "6.0.0",
|
"@typescript-eslint/eslint-plugin": "6.5.0",
|
||||||
"@typescript-eslint/parser": "6.0.0",
|
"@typescript-eslint/parser": "6.5.0",
|
||||||
"autoprefixer": "10.4.14",
|
"autoprefixer": "10.4.14",
|
||||||
"babel-loader": "9.1.3",
|
"babel-loader": "9.1.3",
|
||||||
"babel-plugin-inline-classnames": "2.0.1",
|
"babel-plugin-inline-classnames": "2.0.1",
|
||||||
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
|
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
|
||||||
"core-js": "3.31.1",
|
"core-js": "3.32.1",
|
||||||
"css-loader": "6.7.3",
|
"css-loader": "6.8.1",
|
||||||
"css-modules-typescript-loader": "4.0.1",
|
"css-modules-typescript-loader": "4.0.1",
|
||||||
"eslint": "8.44.0",
|
"eslint": "8.44.0",
|
||||||
"eslint-config-prettier": "8.8.0",
|
"eslint-config-prettier": "8.8.0",
|
||||||
@@ -120,10 +118,10 @@
|
|||||||
"file-loader": "6.2.0",
|
"file-loader": "6.2.0",
|
||||||
"filemanager-webpack-plugin": "8.0.0",
|
"filemanager-webpack-plugin": "8.0.0",
|
||||||
"fork-ts-checker-webpack-plugin": "8.0.0",
|
"fork-ts-checker-webpack-plugin": "8.0.0",
|
||||||
"html-webpack-plugin": "5.5.1",
|
"html-webpack-plugin": "5.5.3",
|
||||||
"loader-utils": "^3.2.1",
|
"loader-utils": "^3.2.1",
|
||||||
"mini-css-extract-plugin": "2.7.5",
|
"mini-css-extract-plugin": "2.7.6",
|
||||||
"postcss": "8.4.23",
|
"postcss": "8.4.31",
|
||||||
"postcss-color-function": "4.1.0",
|
"postcss-color-function": "4.1.0",
|
||||||
"postcss-loader": "7.3.0",
|
"postcss-loader": "7.3.0",
|
||||||
"postcss-mixins": "9.0.4",
|
"postcss-mixins": "9.0.4",
|
||||||
@@ -135,14 +133,14 @@
|
|||||||
"rimraf": "4.4.1",
|
"rimraf": "4.4.1",
|
||||||
"run-sequence": "2.2.1",
|
"run-sequence": "2.2.1",
|
||||||
"streamqueue": "1.1.2",
|
"streamqueue": "1.1.2",
|
||||||
"style-loader": "3.3.2",
|
"style-loader": "3.3.3",
|
||||||
"stylelint": "15.10.1",
|
"stylelint": "15.10.3",
|
||||||
"stylelint-order": "6.0.3",
|
"stylelint-order": "6.0.3",
|
||||||
"terser-webpack-plugin": "5.3.9",
|
"terser-webpack-plugin": "5.3.9",
|
||||||
"ts-loader": "9.4.3",
|
"ts-loader": "9.4.4",
|
||||||
"typescript-plugin-css-modules": "5.0.1",
|
"typescript-plugin-css-modules": "5.0.1",
|
||||||
"url-loader": "4.1.1",
|
"url-loader": "4.1.1",
|
||||||
"webpack": "5.88.1",
|
"webpack": "5.88.2",
|
||||||
"webpack-cli": "5.1.4",
|
"webpack-cli": "5.1.4",
|
||||||
"webpack-livereload-plugin": "3.0.2",
|
"webpack-livereload-plugin": "3.0.2",
|
||||||
"worker-loader": "3.0.8"
|
"worker-loader": "3.0.8"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<PackageVersion Include="AutoFixture" Version="4.17.0" />
|
<PackageVersion Include="AutoFixture" Version="4.17.0" />
|
||||||
<PackageVersion Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" PrivateAssets="all" />
|
<PackageVersion Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" PrivateAssets="all" />
|
||||||
<PackageVersion Include="Dapper" Version="2.0.123" />
|
<PackageVersion Include="Dapper" Version="2.0.123" />
|
||||||
<PackageVersion Include="DryIoc.dll" Version="5.4.0" />
|
<PackageVersion Include="DryIoc.dll" Version="5.4.3" />
|
||||||
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" />
|
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" />
|
||||||
<PackageVersion Include="Equ" Version="2.3.0" />
|
<PackageVersion Include="Equ" Version="2.3.0" />
|
||||||
<PackageVersion Include="FluentAssertions" Version="5.10.3" />
|
<PackageVersion Include="FluentAssertions" Version="5.10.3" />
|
||||||
@@ -16,11 +16,11 @@
|
|||||||
<PackageVersion Include="ImpromptuInterface" Version="7.0.1" />
|
<PackageVersion Include="ImpromptuInterface" Version="7.0.1" />
|
||||||
<PackageVersion Include="LazyCache" Version="2.4.0" />
|
<PackageVersion Include="LazyCache" Version="2.4.0" />
|
||||||
<PackageVersion Include="Mailkit" Version="3.6.0" />
|
<PackageVersion Include="Mailkit" Version="3.6.0" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.16" />
|
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.25" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.1" />
|
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||||
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
<PackageVersion Include="NLog.Extensions.Logging" Version="5.2.3" />
|
<PackageVersion Include="NLog.Extensions.Logging" Version="5.2.3" />
|
||||||
<PackageVersion Include="NLog" Version="5.1.4" />
|
<PackageVersion Include="NLog" Version="5.1.4" />
|
||||||
<PackageVersion Include="NLog.Targets.Syslog" Version="7.0.0" />
|
<PackageVersion Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||||
<PackageVersion Include="Npgsql" Version="7.0.4" />
|
<PackageVersion Include="Npgsql" Version="7.0.6" />
|
||||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" />
|
<PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||||
<PackageVersion Include="NunitXml.TestLogger" Version="3.0.117" />
|
<PackageVersion Include="NunitXml.TestLogger" Version="3.0.117" />
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
|
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
|
||||||
<PackageVersion Include="Sentry" Version="3.31.0" />
|
<PackageVersion Include="Sentry" Version="3.31.0" />
|
||||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.0.1" />
|
<PackageVersion Include="SixLabors.ImageSharp" Version="3.0.2" />
|
||||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
|
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||||
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
|
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
|
||||||
<PackageVersion Include="System.Buffers" Version="4.5.1" />
|
<PackageVersion Include="System.Buffers" Version="4.5.1" />
|
||||||
@@ -57,10 +57,10 @@
|
|||||||
<PackageVersion Include="System.Resources.Extensions" Version="6.0.0" />
|
<PackageVersion Include="System.Resources.Extensions" Version="6.0.0" />
|
||||||
<PackageVersion Include="System.Runtime.Loader" Version="4.3.0" />
|
<PackageVersion Include="System.Runtime.Loader" Version="4.3.0" />
|
||||||
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
|
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
|
||||||
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="6.0.0" />
|
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="6.0.1" />
|
||||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
<PackageVersion Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
||||||
<PackageVersion Include="System.Text.Json" Version="6.0.7" />
|
<PackageVersion Include="System.Text.Json" Version="6.0.9" />
|
||||||
<PackageVersion Include="System.ValueTuple" Version="4.5.0" />
|
<PackageVersion Include="System.ValueTuple" Version="4.5.0" />
|
||||||
<PackageVersion Include="TagLibSharp-Lidarr" Version="2.2.0.19" />
|
<PackageVersion Include="TagLibSharp-Lidarr" Version="2.2.0.19" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -6,6 +6,7 @@ using System.Linq;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NLog;
|
using NLog;
|
||||||
@@ -114,21 +115,21 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_execute_simple_get()
|
public async Task should_execute_simple_get()
|
||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||||
|
|
||||||
var response = Subject.Execute(request);
|
var response = await Subject.ExecuteAsync(request);
|
||||||
|
|
||||||
response.Content.Should().NotBeNullOrWhiteSpace();
|
response.Content.Should().NotBeNullOrWhiteSpace();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_execute_https_get()
|
public async Task should_execute_https_get()
|
||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||||
|
|
||||||
var response = Subject.Execute(request);
|
var response = await Subject.ExecuteAsync(request);
|
||||||
|
|
||||||
response.Content.Should().NotBeNullOrWhiteSpace();
|
response.Content.Should().NotBeNullOrWhiteSpace();
|
||||||
}
|
}
|
||||||
@@ -140,47 +141,47 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
Mocker.GetMock<IConfigService>().SetupGet(x => x.CertificateValidation).Returns(validationType);
|
Mocker.GetMock<IConfigService>().SetupGet(x => x.CertificateValidation).Returns(validationType);
|
||||||
var request = new HttpRequest($"https://expired.badssl.com");
|
var request = new HttpRequest($"https://expired.badssl.com");
|
||||||
|
|
||||||
Assert.Throws<HttpRequestException>(() => Subject.Execute(request));
|
Assert.ThrowsAsync<HttpRequestException>(async () => await Subject.ExecuteAsync(request));
|
||||||
ExceptionVerification.ExpectedErrors(1);
|
ExceptionVerification.ExpectedErrors(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void bad_ssl_should_pass_if_remote_validation_disabled()
|
public async Task bad_ssl_should_pass_if_remote_validation_disabled()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IConfigService>().SetupGet(x => x.CertificateValidation).Returns(CertificateValidationType.Disabled);
|
Mocker.GetMock<IConfigService>().SetupGet(x => x.CertificateValidation).Returns(CertificateValidationType.Disabled);
|
||||||
|
|
||||||
var request = new HttpRequest($"https://expired.badssl.com");
|
var request = new HttpRequest($"https://expired.badssl.com");
|
||||||
|
|
||||||
Subject.Execute(request);
|
await Subject.ExecuteAsync(request);
|
||||||
ExceptionVerification.ExpectedErrors(0);
|
ExceptionVerification.ExpectedErrors(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_execute_typed_get()
|
public async Task should_execute_typed_get()
|
||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/get?test=1");
|
var request = new HttpRequest($"https://{_httpBinHost}/get?test=1");
|
||||||
|
|
||||||
var response = Subject.Get<HttpBinResource>(request);
|
var response = await Subject.GetAsync<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Url.EndsWith("/get?test=1");
|
response.Resource.Url.EndsWith("/get?test=1");
|
||||||
response.Resource.Args.Should().Contain("test", "1");
|
response.Resource.Args.Should().Contain("test", "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_execute_simple_post()
|
public async Task should_execute_simple_post()
|
||||||
{
|
{
|
||||||
var message = "{ my: 1 }";
|
var message = "{ my: 1 }";
|
||||||
|
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/post");
|
var request = new HttpRequest($"https://{_httpBinHost}/post");
|
||||||
request.SetContent(message);
|
request.SetContent(message);
|
||||||
|
|
||||||
var response = Subject.Post<HttpBinResource>(request);
|
var response = await Subject.PostAsync<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Data.Should().Be(message);
|
response.Resource.Data.Should().Be(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_execute_post_with_content_type()
|
public async Task should_execute_post_with_content_type()
|
||||||
{
|
{
|
||||||
var message = "{ my: 1 }";
|
var message = "{ my: 1 }";
|
||||||
|
|
||||||
@@ -188,17 +189,16 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
request.SetContent(message);
|
request.SetContent(message);
|
||||||
request.Headers.ContentType = "application/json";
|
request.Headers.ContentType = "application/json";
|
||||||
|
|
||||||
var response = Subject.Post<HttpBinResource>(request);
|
var response = await Subject.PostAsync<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Data.Should().Be(message);
|
response.Resource.Data.Should().Be(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_execute_get_using_gzip()
|
public async Task should_execute_get_using_gzip()
|
||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/gzip");
|
var request = new HttpRequest($"https://{_httpBinHost}/gzip");
|
||||||
|
var response = await Subject.GetAsync<HttpBinResource>(request);
|
||||||
var response = Subject.Get<HttpBinResource>(request);
|
|
||||||
|
|
||||||
response.Resource.Headers["Accept-Encoding"].ToString().Should().Contain("gzip");
|
response.Resource.Headers["Accept-Encoding"].ToString().Should().Contain("gzip");
|
||||||
|
|
||||||
@@ -208,11 +208,10 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
[Platform(Exclude = "MacOsX", Reason = "Azure agent update prevents brotli on OSX")]
|
[Platform(Exclude = "MacOsX", Reason = "Azure agent update prevents brotli on OSX")]
|
||||||
public void should_execute_get_using_brotli()
|
public async Task should_execute_get_using_brotli()
|
||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/brotli");
|
var request = new HttpRequest($"https://{_httpBinHost}/brotli");
|
||||||
|
var response = await Subject.GetAsync<HttpBinResource>(request);
|
||||||
var response = Subject.Get<HttpBinResource>(request);
|
|
||||||
|
|
||||||
response.Resource.Headers["Accept-Encoding"].ToString().Should().Contain("br");
|
response.Resource.Headers["Accept-Encoding"].ToString().Should().Contain("br");
|
||||||
|
|
||||||
@@ -230,7 +229,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/status/{statusCode}");
|
var request = new HttpRequest($"https://{_httpBinHost}/status/{statusCode}");
|
||||||
|
|
||||||
var exception = Assert.Throws<HttpException>(() => Subject.Get<HttpBinResource>(request));
|
var exception = Assert.ThrowsAsync<HttpException>(async () => await Subject.GetAsync<HttpBinResource>(request));
|
||||||
|
|
||||||
((int)exception.Response.StatusCode).Should().Be(statusCode);
|
((int)exception.Response.StatusCode).Should().Be(statusCode);
|
||||||
|
|
||||||
@@ -243,7 +242,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
var request = new HttpRequest($"https://{_httpBinHost}/status/{HttpStatusCode.NotFound}");
|
var request = new HttpRequest($"https://{_httpBinHost}/status/{HttpStatusCode.NotFound}");
|
||||||
request.SuppressHttpErrorStatusCodes = new[] { HttpStatusCode.NotFound };
|
request.SuppressHttpErrorStatusCodes = new[] { HttpStatusCode.NotFound };
|
||||||
|
|
||||||
var exception = Assert.Throws<HttpException>(() => Subject.Get<HttpBinResource>(request));
|
Assert.ThrowsAsync<HttpException>(async () => await Subject.GetAsync<HttpBinResource>(request));
|
||||||
|
|
||||||
ExceptionVerification.IgnoreWarns();
|
ExceptionVerification.IgnoreWarns();
|
||||||
}
|
}
|
||||||
@@ -253,7 +252,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/status/{HttpStatusCode.NotFound}");
|
var request = new HttpRequest($"https://{_httpBinHost}/status/{HttpStatusCode.NotFound}");
|
||||||
|
|
||||||
var exception = Assert.Throws<HttpException>(() => Subject.Get<HttpBinResource>(request));
|
var exception = Assert.ThrowsAsync<HttpException>(async () => await Subject.GetAsync<HttpBinResource>(request));
|
||||||
|
|
||||||
ExceptionVerification.ExpectedWarns(1);
|
ExceptionVerification.ExpectedWarns(1);
|
||||||
}
|
}
|
||||||
@@ -264,28 +263,28 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
var request = new HttpRequest($"https://{_httpBinHost}/status/{HttpStatusCode.NotFound}");
|
var request = new HttpRequest($"https://{_httpBinHost}/status/{HttpStatusCode.NotFound}");
|
||||||
request.LogHttpError = false;
|
request.LogHttpError = false;
|
||||||
|
|
||||||
var exception = Assert.Throws<HttpException>(() => Subject.Get<HttpBinResource>(request));
|
Assert.ThrowsAsync<HttpException>(async () => await Subject.GetAsync<HttpBinResource>(request));
|
||||||
|
|
||||||
ExceptionVerification.ExpectedWarns(0);
|
ExceptionVerification.ExpectedWarns(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_follow_redirects_when_not_in_production()
|
public async Task should_not_follow_redirects_when_not_in_production()
|
||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/redirect/1");
|
var request = new HttpRequest($"https://{_httpBinHost}/redirect/1");
|
||||||
|
|
||||||
Subject.Get(request);
|
await Subject.GetAsync(request);
|
||||||
|
|
||||||
ExceptionVerification.ExpectedErrors(1);
|
ExceptionVerification.ExpectedErrors(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_follow_redirects()
|
public async Task should_follow_redirects()
|
||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/redirect/1");
|
var request = new HttpRequest($"https://{_httpBinHost}/redirect/1");
|
||||||
request.AllowAutoRedirect = true;
|
request.AllowAutoRedirect = true;
|
||||||
|
|
||||||
var response = Subject.Get(request);
|
var response = await Subject.GetAsync(request);
|
||||||
|
|
||||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||||
|
|
||||||
@@ -293,12 +292,12 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_follow_redirects()
|
public async Task should_not_follow_redirects()
|
||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/redirect/1");
|
var request = new HttpRequest($"https://{_httpBinHost}/redirect/1");
|
||||||
request.AllowAutoRedirect = false;
|
request.AllowAutoRedirect = false;
|
||||||
|
|
||||||
var response = Subject.Get(request);
|
var response = await Subject.GetAsync(request);
|
||||||
|
|
||||||
response.StatusCode.Should().Be(HttpStatusCode.Found);
|
response.StatusCode.Should().Be(HttpStatusCode.Found);
|
||||||
|
|
||||||
@@ -306,14 +305,14 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_follow_redirects_to_https()
|
public async Task should_follow_redirects_to_https()
|
||||||
{
|
{
|
||||||
var request = new HttpRequestBuilder($"https://{_httpBinHost}/redirect-to")
|
var request = new HttpRequestBuilder($"https://{_httpBinHost}/redirect-to")
|
||||||
.AddQueryParam("url", $"https://readarr.com/")
|
.AddQueryParam("url", $"https://readarr.com/")
|
||||||
.Build();
|
.Build();
|
||||||
request.AllowAutoRedirect = true;
|
request.AllowAutoRedirect = true;
|
||||||
|
|
||||||
var response = Subject.Get(request);
|
var response = await Subject.GetAsync(request);
|
||||||
|
|
||||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||||
response.Content.Should().Contain("Readarr");
|
response.Content.Should().Contain("Readarr");
|
||||||
@@ -327,17 +326,17 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
var request = new HttpRequest($"https://{_httpBinHost}/redirect/6");
|
var request = new HttpRequest($"https://{_httpBinHost}/redirect/6");
|
||||||
request.AllowAutoRedirect = true;
|
request.AllowAutoRedirect = true;
|
||||||
|
|
||||||
Assert.Throws<WebException>(() => Subject.Get(request));
|
Assert.ThrowsAsync<WebException>(async () => await Subject.GetAsync(request));
|
||||||
|
|
||||||
ExceptionVerification.ExpectedErrors(0);
|
ExceptionVerification.ExpectedErrors(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_send_user_agent()
|
public async Task should_send_user_agent()
|
||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||||
|
|
||||||
var response = Subject.Get<HttpBinResource>(request);
|
var response = await Subject.GetAsync<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Headers.Should().ContainKey("User-Agent");
|
response.Resource.Headers.Should().ContainKey("User-Agent");
|
||||||
|
|
||||||
@@ -347,24 +346,24 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("Accept", "text/xml, text/rss+xml, application/rss+xml")]
|
[TestCase("Accept", "text/xml, text/rss+xml, application/rss+xml")]
|
||||||
public void should_send_headers(string header, string value)
|
public async Task should_send_headers(string header, string value)
|
||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||||
request.Headers.Add(header, value);
|
request.Headers.Add(header, value);
|
||||||
|
|
||||||
var response = Subject.Get<HttpBinResource>(request);
|
var response = await Subject.GetAsync<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Headers[header].ToString().Should().Be(value);
|
response.Resource.Headers[header].ToString().Should().Be(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_download_file()
|
public async Task should_download_file()
|
||||||
{
|
{
|
||||||
var file = GetTempFilePath();
|
var file = GetTempFilePath();
|
||||||
|
|
||||||
var url = "https://readarr.com/img/slider/artistdetails.png";
|
var url = "https://readarr.com/img/slider/artistdetails.png";
|
||||||
|
|
||||||
Subject.DownloadFile(url, file);
|
await Subject.DownloadFileAsync(url, file);
|
||||||
|
|
||||||
var fileInfo = new FileInfo(file);
|
var fileInfo = new FileInfo(file);
|
||||||
fileInfo.Exists.Should().BeTrue();
|
fileInfo.Exists.Should().BeTrue();
|
||||||
@@ -372,7 +371,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_download_file_with_redirect()
|
public async Task should_download_file_with_redirect()
|
||||||
{
|
{
|
||||||
var file = GetTempFilePath();
|
var file = GetTempFilePath();
|
||||||
|
|
||||||
@@ -380,7 +379,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
.AddQueryParam("url", $"https://readarr.com/img/slider/artistdetails.png")
|
.AddQueryParam("url", $"https://readarr.com/img/slider/artistdetails.png")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
Subject.DownloadFile(request.Url.FullUri, file);
|
await Subject.DownloadFileAsync(request.Url.FullUri, file);
|
||||||
|
|
||||||
ExceptionVerification.ExpectedErrors(0);
|
ExceptionVerification.ExpectedErrors(0);
|
||||||
|
|
||||||
@@ -394,7 +393,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
{
|
{
|
||||||
var file = GetTempFilePath();
|
var file = GetTempFilePath();
|
||||||
|
|
||||||
Assert.Throws<HttpException>(() => Subject.DownloadFile("https://download.sonarr.tv/wrongpath", file));
|
Assert.ThrowsAsync<HttpException>(async () => await Subject.DownloadFileAsync("https://download.sonarr.tv/wrongpath", file));
|
||||||
|
|
||||||
File.Exists(file).Should().BeFalse();
|
File.Exists(file).Should().BeFalse();
|
||||||
|
|
||||||
@@ -402,7 +401,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_write_redirect_content_to_stream()
|
public async Task should_not_write_redirect_content_to_stream()
|
||||||
{
|
{
|
||||||
var file = GetTempFilePath();
|
var file = GetTempFilePath();
|
||||||
|
|
||||||
@@ -412,7 +411,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
request.AllowAutoRedirect = false;
|
request.AllowAutoRedirect = false;
|
||||||
request.ResponseStream = fileStream;
|
request.ResponseStream = fileStream;
|
||||||
|
|
||||||
var response = Subject.Get(request);
|
var response = await Subject.GetAsync(request);
|
||||||
|
|
||||||
response.StatusCode.Should().Be(HttpStatusCode.Moved);
|
response.StatusCode.Should().Be(HttpStatusCode.Moved);
|
||||||
}
|
}
|
||||||
@@ -427,12 +426,12 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_send_cookie()
|
public async Task should_send_cookie()
|
||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||||
request.Cookies["my"] = "cookie";
|
request.Cookies["my"] = "cookie";
|
||||||
|
|
||||||
var response = Subject.Get<HttpBinResource>(request);
|
var response = await Subject.GetAsync<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Headers.Should().ContainKey("Cookie");
|
response.Resource.Headers.Should().ContainKey("Cookie");
|
||||||
|
|
||||||
@@ -461,13 +460,13 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_preserve_cookie_during_session()
|
public async Task should_preserve_cookie_during_session()
|
||||||
{
|
{
|
||||||
GivenOldCookie();
|
GivenOldCookie();
|
||||||
|
|
||||||
var request = new HttpRequest($"https://{_httpBinHost2}/get");
|
var request = new HttpRequest($"https://{_httpBinHost2}/get");
|
||||||
|
|
||||||
var response = Subject.Get<HttpBinResource>(request);
|
var response = await Subject.GetAsync<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Headers.Should().ContainKey("Cookie");
|
response.Resource.Headers.Should().ContainKey("Cookie");
|
||||||
|
|
||||||
@@ -477,30 +476,30 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_send_cookie_to_other_host()
|
public async Task should_not_send_cookie_to_other_host()
|
||||||
{
|
{
|
||||||
GivenOldCookie();
|
GivenOldCookie();
|
||||||
|
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||||
|
|
||||||
var response = Subject.Get<HttpBinResource>(request);
|
var response = await Subject.GetAsync<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Headers.Should().NotContainKey("Cookie");
|
response.Resource.Headers.Should().NotContainKey("Cookie");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_store_request_cookie()
|
public async Task should_not_store_request_cookie()
|
||||||
{
|
{
|
||||||
var requestGet = new HttpRequest($"https://{_httpBinHost}/get");
|
var requestGet = new HttpRequest($"https://{_httpBinHost}/get");
|
||||||
requestGet.Cookies.Add("my", "cookie");
|
requestGet.Cookies.Add("my", "cookie");
|
||||||
requestGet.AllowAutoRedirect = false;
|
requestGet.AllowAutoRedirect = false;
|
||||||
requestGet.StoreRequestCookie = false;
|
requestGet.StoreRequestCookie = false;
|
||||||
requestGet.StoreResponseCookie = false;
|
requestGet.StoreResponseCookie = false;
|
||||||
var responseGet = Subject.Get<HttpBinResource>(requestGet);
|
var responseGet = await Subject.GetAsync<HttpBinResource>(requestGet);
|
||||||
|
|
||||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||||
requestCookies.AllowAutoRedirect = false;
|
requestCookies.AllowAutoRedirect = false;
|
||||||
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().BeEmpty();
|
responseCookies.Resource.Cookies.Should().BeEmpty();
|
||||||
|
|
||||||
@@ -508,18 +507,18 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_store_request_cookie()
|
public async Task should_store_request_cookie()
|
||||||
{
|
{
|
||||||
var requestGet = new HttpRequest($"https://{_httpBinHost}/get");
|
var requestGet = new HttpRequest($"https://{_httpBinHost}/get");
|
||||||
requestGet.Cookies.Add("my", "cookie");
|
requestGet.Cookies.Add("my", "cookie");
|
||||||
requestGet.AllowAutoRedirect = false;
|
requestGet.AllowAutoRedirect = false;
|
||||||
requestGet.StoreRequestCookie.Should().BeTrue();
|
requestGet.StoreRequestCookie.Should().BeTrue();
|
||||||
requestGet.StoreResponseCookie = false;
|
requestGet.StoreResponseCookie = false;
|
||||||
var responseGet = Subject.Get<HttpBinResource>(requestGet);
|
var responseGet = await Subject.GetAsync<HttpBinResource>(requestGet);
|
||||||
|
|
||||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||||
requestCookies.AllowAutoRedirect = false;
|
requestCookies.AllowAutoRedirect = false;
|
||||||
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||||
|
|
||||||
@@ -527,7 +526,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_delete_request_cookie()
|
public async Task should_delete_request_cookie()
|
||||||
{
|
{
|
||||||
var requestDelete = new HttpRequest($"https://{_httpBinHost}/cookies/delete?my");
|
var requestDelete = new HttpRequest($"https://{_httpBinHost}/cookies/delete?my");
|
||||||
requestDelete.Cookies.Add("my", "cookie");
|
requestDelete.Cookies.Add("my", "cookie");
|
||||||
@@ -536,13 +535,13 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
requestDelete.StoreResponseCookie = false;
|
requestDelete.StoreResponseCookie = false;
|
||||||
|
|
||||||
// Delete and redirect since that's the only way to check the internal temporary cookie container
|
// Delete and redirect since that's the only way to check the internal temporary cookie container
|
||||||
var responseCookies = Subject.Get<HttpCookieResource>(requestDelete);
|
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestDelete);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().BeEmpty();
|
responseCookies.Resource.Cookies.Should().BeEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_clear_request_cookie()
|
public async Task should_clear_request_cookie()
|
||||||
{
|
{
|
||||||
var requestSet = new HttpRequest($"https://{_httpBinHost}/cookies");
|
var requestSet = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||||
requestSet.Cookies.Add("my", "cookie");
|
requestSet.Cookies.Add("my", "cookie");
|
||||||
@@ -550,7 +549,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
requestSet.StoreRequestCookie = true;
|
requestSet.StoreRequestCookie = true;
|
||||||
requestSet.StoreResponseCookie = false;
|
requestSet.StoreResponseCookie = false;
|
||||||
|
|
||||||
var responseSet = Subject.Get<HttpCookieResource>(requestSet);
|
var responseSet = await Subject.GetAsync<HttpCookieResource>(requestSet);
|
||||||
|
|
||||||
var requestClear = new HttpRequest($"https://{_httpBinHost}/cookies");
|
var requestClear = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||||
requestClear.Cookies.Add("my", null);
|
requestClear.Cookies.Add("my", null);
|
||||||
@@ -558,24 +557,24 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
requestClear.StoreRequestCookie = true;
|
requestClear.StoreRequestCookie = true;
|
||||||
requestClear.StoreResponseCookie = false;
|
requestClear.StoreResponseCookie = false;
|
||||||
|
|
||||||
var responseClear = Subject.Get<HttpCookieResource>(requestClear);
|
var responseClear = await Subject.GetAsync<HttpCookieResource>(requestClear);
|
||||||
|
|
||||||
responseClear.Resource.Cookies.Should().BeEmpty();
|
responseClear.Resource.Cookies.Should().BeEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_store_response_cookie()
|
public async Task should_not_store_response_cookie()
|
||||||
{
|
{
|
||||||
var requestSet = new HttpRequest($"https://{_httpBinHost}/cookies/set?my=cookie");
|
var requestSet = new HttpRequest($"https://{_httpBinHost}/cookies/set?my=cookie");
|
||||||
requestSet.AllowAutoRedirect = false;
|
requestSet.AllowAutoRedirect = false;
|
||||||
requestSet.StoreRequestCookie = false;
|
requestSet.StoreRequestCookie = false;
|
||||||
requestSet.StoreResponseCookie.Should().BeFalse();
|
requestSet.StoreResponseCookie.Should().BeFalse();
|
||||||
|
|
||||||
var responseSet = Subject.Get(requestSet);
|
var responseSet = await Subject.GetAsync(requestSet);
|
||||||
|
|
||||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||||
|
|
||||||
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().BeEmpty();
|
responseCookies.Resource.Cookies.Should().BeEmpty();
|
||||||
|
|
||||||
@@ -583,18 +582,18 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_store_response_cookie()
|
public async Task should_store_response_cookie()
|
||||||
{
|
{
|
||||||
var requestSet = new HttpRequest($"https://{_httpBinHost}/cookies/set?my=cookie");
|
var requestSet = new HttpRequest($"https://{_httpBinHost}/cookies/set?my=cookie");
|
||||||
requestSet.AllowAutoRedirect = false;
|
requestSet.AllowAutoRedirect = false;
|
||||||
requestSet.StoreRequestCookie = false;
|
requestSet.StoreRequestCookie = false;
|
||||||
requestSet.StoreResponseCookie = true;
|
requestSet.StoreResponseCookie = true;
|
||||||
|
|
||||||
var responseSet = Subject.Get(requestSet);
|
var responseSet = await Subject.GetAsync(requestSet);
|
||||||
|
|
||||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||||
|
|
||||||
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||||
|
|
||||||
@@ -602,13 +601,13 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_temp_store_response_cookie()
|
public async Task should_temp_store_response_cookie()
|
||||||
{
|
{
|
||||||
var requestSet = new HttpRequest($"https://{_httpBinHost}/cookies/set?my=cookie");
|
var requestSet = new HttpRequest($"https://{_httpBinHost}/cookies/set?my=cookie");
|
||||||
requestSet.AllowAutoRedirect = true;
|
requestSet.AllowAutoRedirect = true;
|
||||||
requestSet.StoreRequestCookie = false;
|
requestSet.StoreRequestCookie = false;
|
||||||
requestSet.StoreResponseCookie.Should().BeFalse();
|
requestSet.StoreResponseCookie.Should().BeFalse();
|
||||||
var responseSet = Subject.Get<HttpCookieResource>(requestSet);
|
var responseSet = await Subject.GetAsync<HttpCookieResource>(requestSet);
|
||||||
|
|
||||||
// Set and redirect since that's the only way to check the internal temporary cookie container
|
// Set and redirect since that's the only way to check the internal temporary cookie container
|
||||||
responseSet.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
responseSet.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||||
@@ -617,7 +616,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_overwrite_response_cookie()
|
public async Task should_overwrite_response_cookie()
|
||||||
{
|
{
|
||||||
var requestSet = new HttpRequest($"https://{_httpBinHost}/cookies/set?my=cookie");
|
var requestSet = new HttpRequest($"https://{_httpBinHost}/cookies/set?my=cookie");
|
||||||
requestSet.Cookies.Add("my", "oldcookie");
|
requestSet.Cookies.Add("my", "oldcookie");
|
||||||
@@ -625,11 +624,11 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
requestSet.StoreRequestCookie = false;
|
requestSet.StoreRequestCookie = false;
|
||||||
requestSet.StoreResponseCookie = true;
|
requestSet.StoreResponseCookie = true;
|
||||||
|
|
||||||
var responseSet = Subject.Get(requestSet);
|
var responseSet = await Subject.GetAsync(requestSet);
|
||||||
|
|
||||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||||
|
|
||||||
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||||
|
|
||||||
@@ -637,7 +636,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_overwrite_temp_response_cookie()
|
public async Task should_overwrite_temp_response_cookie()
|
||||||
{
|
{
|
||||||
var requestSet = new HttpRequest($"https://{_httpBinHost}/cookies/set?my=cookie");
|
var requestSet = new HttpRequest($"https://{_httpBinHost}/cookies/set?my=cookie");
|
||||||
requestSet.Cookies.Add("my", "oldcookie");
|
requestSet.Cookies.Add("my", "oldcookie");
|
||||||
@@ -645,13 +644,13 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
requestSet.StoreRequestCookie = true;
|
requestSet.StoreRequestCookie = true;
|
||||||
requestSet.StoreResponseCookie = false;
|
requestSet.StoreResponseCookie = false;
|
||||||
|
|
||||||
var responseSet = Subject.Get<HttpCookieResource>(requestSet);
|
var responseSet = await Subject.GetAsync<HttpCookieResource>(requestSet);
|
||||||
|
|
||||||
responseSet.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
responseSet.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||||
|
|
||||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||||
|
|
||||||
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "oldcookie");
|
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "oldcookie");
|
||||||
|
|
||||||
@@ -659,14 +658,14 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_delete_response_cookie()
|
public async Task should_not_delete_response_cookie()
|
||||||
{
|
{
|
||||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||||
requestCookies.Cookies.Add("my", "cookie");
|
requestCookies.Cookies.Add("my", "cookie");
|
||||||
requestCookies.AllowAutoRedirect = false;
|
requestCookies.AllowAutoRedirect = false;
|
||||||
requestCookies.StoreRequestCookie = true;
|
requestCookies.StoreRequestCookie = true;
|
||||||
requestCookies.StoreResponseCookie = false;
|
requestCookies.StoreResponseCookie = false;
|
||||||
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||||
|
|
||||||
@@ -675,13 +674,13 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
requestDelete.StoreRequestCookie = false;
|
requestDelete.StoreRequestCookie = false;
|
||||||
requestDelete.StoreResponseCookie = false;
|
requestDelete.StoreResponseCookie = false;
|
||||||
|
|
||||||
var responseDelete = Subject.Get(requestDelete);
|
var responseDelete = await Subject.GetAsync(requestDelete);
|
||||||
|
|
||||||
requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||||
requestCookies.StoreRequestCookie = false;
|
requestCookies.StoreRequestCookie = false;
|
||||||
requestCookies.StoreResponseCookie = false;
|
requestCookies.StoreResponseCookie = false;
|
||||||
|
|
||||||
responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||||
|
|
||||||
@@ -689,14 +688,14 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_delete_response_cookie()
|
public async Task should_delete_response_cookie()
|
||||||
{
|
{
|
||||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||||
requestCookies.Cookies.Add("my", "cookie");
|
requestCookies.Cookies.Add("my", "cookie");
|
||||||
requestCookies.AllowAutoRedirect = false;
|
requestCookies.AllowAutoRedirect = false;
|
||||||
requestCookies.StoreRequestCookie = true;
|
requestCookies.StoreRequestCookie = true;
|
||||||
requestCookies.StoreResponseCookie = false;
|
requestCookies.StoreResponseCookie = false;
|
||||||
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||||
|
|
||||||
@@ -705,13 +704,13 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
requestDelete.StoreRequestCookie = false;
|
requestDelete.StoreRequestCookie = false;
|
||||||
requestDelete.StoreResponseCookie = true;
|
requestDelete.StoreResponseCookie = true;
|
||||||
|
|
||||||
var responseDelete = Subject.Get(requestDelete);
|
var responseDelete = await Subject.GetAsync(requestDelete);
|
||||||
|
|
||||||
requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||||
requestCookies.StoreRequestCookie = false;
|
requestCookies.StoreRequestCookie = false;
|
||||||
requestCookies.StoreResponseCookie = false;
|
requestCookies.StoreResponseCookie = false;
|
||||||
|
|
||||||
responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().BeEmpty();
|
responseCookies.Resource.Cookies.Should().BeEmpty();
|
||||||
|
|
||||||
@@ -719,14 +718,14 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_delete_temp_response_cookie()
|
public async Task should_delete_temp_response_cookie()
|
||||||
{
|
{
|
||||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||||
requestCookies.Cookies.Add("my", "cookie");
|
requestCookies.Cookies.Add("my", "cookie");
|
||||||
requestCookies.AllowAutoRedirect = false;
|
requestCookies.AllowAutoRedirect = false;
|
||||||
requestCookies.StoreRequestCookie = true;
|
requestCookies.StoreRequestCookie = true;
|
||||||
requestCookies.StoreResponseCookie = false;
|
requestCookies.StoreResponseCookie = false;
|
||||||
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||||
|
|
||||||
@@ -734,7 +733,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
requestDelete.AllowAutoRedirect = true;
|
requestDelete.AllowAutoRedirect = true;
|
||||||
requestDelete.StoreRequestCookie = false;
|
requestDelete.StoreRequestCookie = false;
|
||||||
requestDelete.StoreResponseCookie = false;
|
requestDelete.StoreResponseCookie = false;
|
||||||
var responseDelete = Subject.Get<HttpCookieResource>(requestDelete);
|
var responseDelete = await Subject.GetAsync<HttpCookieResource>(requestDelete);
|
||||||
|
|
||||||
responseDelete.Resource.Cookies.Should().BeEmpty();
|
responseDelete.Resource.Cookies.Should().BeEmpty();
|
||||||
|
|
||||||
@@ -752,13 +751,13 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/status/429");
|
var request = new HttpRequest($"https://{_httpBinHost}/status/429");
|
||||||
|
|
||||||
Assert.Throws<TooManyRequestsException>(() => Subject.Get(request));
|
Assert.ThrowsAsync<TooManyRequestsException>(async () => await Subject.GetAsync(request));
|
||||||
|
|
||||||
ExceptionVerification.IgnoreWarns();
|
ExceptionVerification.IgnoreWarns();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_call_interceptor()
|
public async Task should_call_interceptor()
|
||||||
{
|
{
|
||||||
Mocker.SetConstant<IEnumerable<IHttpRequestInterceptor>>(new[] { Mocker.GetMock<IHttpRequestInterceptor>().Object });
|
Mocker.SetConstant<IEnumerable<IHttpRequestInterceptor>>(new[] { Mocker.GetMock<IHttpRequestInterceptor>().Object });
|
||||||
|
|
||||||
@@ -772,7 +771,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
|
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||||
|
|
||||||
Subject.Get(request);
|
await Subject.GetAsync(request);
|
||||||
|
|
||||||
Mocker.GetMock<IHttpRequestInterceptor>()
|
Mocker.GetMock<IHttpRequestInterceptor>()
|
||||||
.Verify(v => v.PreRequest(It.IsAny<HttpRequest>()), Times.Once());
|
.Verify(v => v.PreRequest(It.IsAny<HttpRequest>()), Times.Once());
|
||||||
@@ -783,7 +782,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
|
|
||||||
[TestCase("en-US")]
|
[TestCase("en-US")]
|
||||||
[TestCase("es-ES")]
|
[TestCase("es-ES")]
|
||||||
public void should_parse_malformed_cloudflare_cookie(string culture)
|
public async Task should_parse_malformed_cloudflare_cookie(string culture)
|
||||||
{
|
{
|
||||||
var origCulture = Thread.CurrentThread.CurrentCulture;
|
var origCulture = Thread.CurrentThread.CurrentCulture;
|
||||||
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(culture);
|
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(culture);
|
||||||
@@ -799,11 +798,11 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
requestSet.AllowAutoRedirect = false;
|
requestSet.AllowAutoRedirect = false;
|
||||||
requestSet.StoreResponseCookie = true;
|
requestSet.StoreResponseCookie = true;
|
||||||
|
|
||||||
var responseSet = Subject.Get(requestSet);
|
var responseSet = await Subject.GetAsync(requestSet);
|
||||||
|
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||||
|
|
||||||
var response = Subject.Get<HttpBinResource>(request);
|
var response = await Subject.GetAsync<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Headers.Should().ContainKey("Cookie");
|
response.Resource.Headers.Should().ContainKey("Cookie");
|
||||||
|
|
||||||
@@ -821,7 +820,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("lang_code=en; expires=Wed, 23-Dec-2026 18:09:14 GMT; Max-Age=31536000; path=/; domain=.abc.com")]
|
[TestCase("lang_code=en; expires=Wed, 23-Dec-2026 18:09:14 GMT; Max-Age=31536000; path=/; domain=.abc.com")]
|
||||||
public void should_reject_malformed_domain_cookie(string malformedCookie)
|
public async Task should_reject_malformed_domain_cookie(string malformedCookie)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -831,11 +830,11 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
requestSet.AllowAutoRedirect = false;
|
requestSet.AllowAutoRedirect = false;
|
||||||
requestSet.StoreResponseCookie = true;
|
requestSet.StoreResponseCookie = true;
|
||||||
|
|
||||||
var responseSet = Subject.Get(requestSet);
|
var responseSet = await Subject.GetAsync(requestSet);
|
||||||
|
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||||
|
|
||||||
var response = Subject.Get<HttpBinResource>(request);
|
var response = await Subject.GetAsync<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Headers.Should().NotContainKey("Cookie");
|
response.Resource.Headers.Should().NotContainKey("Cookie");
|
||||||
|
|
||||||
@@ -847,12 +846,12 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_correctly_use_basic_auth_with_basic_network_credential()
|
public async Task should_correctly_use_basic_auth()
|
||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/basic-auth/username/password");
|
var request = new HttpRequest($"https://{_httpBinHost}/basic-auth/username/password");
|
||||||
request.Credentials = new BasicNetworkCredential("username", "password");
|
request.Credentials = new BasicNetworkCredential("username", "password");
|
||||||
|
|
||||||
var response = Subject.Execute(request);
|
var response = await Subject.ExecuteAsync(request);
|
||||||
|
|
||||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,15 +70,15 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
|||||||
[TestCase(@"[Info] MigrationController: *** Migrating Database=readarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
|
[TestCase(@"[Info] MigrationController: *** Migrating Database=readarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
|
||||||
|
|
||||||
// Announce URLs (passkeys) Magnet & Tracker
|
// Announce URLs (passkeys) Magnet & Tracker
|
||||||
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
|
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210IMAveQL2tyu8xyui%2fannounce""}")]
|
||||||
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2ftracker.php%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
|
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2ftracker.php%2f9pr04sg601233210IMAveQL2tyu8xyui%2fannounce""}")]
|
||||||
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce%2f9pr04sg601233210imaveql2tyu8xyui""}")]
|
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce%2f9pr04sg601233210IMAveQL2tyu8xyui""}")]
|
||||||
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce.php%3fpasskey%3d9pr04sg601233210imaveql2tyu8xyui""}")]
|
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce.php%3fpasskey%3d9pr04sg601233210IMAveQL2tyu8xyui""}")]
|
||||||
[TestCase(@"tracker"":""https://xxx.yyy/9pr04sg601233210imaveql2tyu8xyui/announce""}")]
|
[TestCase(@"tracker"":""https://xxx.yyy/9pr04sg601233210IMAveQL2tyu8xyui/announce""}")]
|
||||||
[TestCase(@"tracker"":""https://xxx.yyy/tracker.php/9pr04sg601233210imaveql2tyu8xyui/announce""}")]
|
[TestCase(@"tracker"":""https://xxx.yyy/tracker.php/9pr04sg601233210IMAveQL2tyu8xyui/announce""}")]
|
||||||
[TestCase(@"tracker"":""https://xxx.yyy/announce/9pr04sg601233210imaveql2tyu8xyui""}")]
|
[TestCase(@"tracker"":""https://xxx.yyy/announce/9pr04sg601233210IMAveQL2tyu8xyui""}")]
|
||||||
[TestCase(@"tracker"":""https://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui""}")]
|
[TestCase(@"tracker"":""https://xxx.yyy/announce.php?passkey=9pr04sg601233210IMAveQL2tyu8xyui""}")]
|
||||||
[TestCase(@"tracker"":""http://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui"",""info"":""http://xxx.yyy/info?a=b""")]
|
[TestCase(@"tracker"":""http://xxx.yyy/announce.php?passkey=9pr04sg601233210IMAveQL2tyu8xyui"",""info"":""http://xxx.yyy/info?a=b""")]
|
||||||
|
|
||||||
// Notifiarr
|
// Notifiarr
|
||||||
[TestCase(@"https://xxx.yyy/api/v1/notification/readarr/9pr04sg6-0123-3210-imav-eql2tyu8xyui")]
|
[TestCase(@"https://xxx.yyy/api/v1/notification/readarr/9pr04sg6-0123-3210-imav-eql2tyu8xyui")]
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ namespace NzbDrone.Common.Extensions
|
|||||||
{
|
{
|
||||||
if (text.IsNullOrWhiteSpace())
|
if (text.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException("text");
|
throw new ArgumentNullException(nameof(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
return text.IndexOfAny(Path.GetInvalidPathChars()) >= 0;
|
return text.IndexOfAny(Path.GetInvalidPathChars()) >= 0;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace NzbDrone.Common.Http.Dispatchers
|
namespace NzbDrone.Common.Http.Dispatchers
|
||||||
{
|
{
|
||||||
public interface IHttpDispatcher
|
public interface IHttpDispatcher
|
||||||
{
|
{
|
||||||
HttpResponse GetResponse(HttpRequest request, CookieContainer cookies);
|
Task<HttpResponse> GetResponseAsync(HttpRequest request, CookieContainer cookies);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,9 +44,13 @@ namespace NzbDrone.Common.Http.Dispatchers
|
|||||||
_credentialCache = cacheManager.GetCache<CredentialCache>(typeof(ManagedHttpDispatcher), "credentialcache");
|
_credentialCache = cacheManager.GetCache<CredentialCache>(typeof(ManagedHttpDispatcher), "credentialcache");
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies)
|
public async Task<HttpResponse> GetResponseAsync(HttpRequest request, CookieContainer cookies)
|
||||||
{
|
{
|
||||||
var requestMessage = new HttpRequestMessage(request.Method, (Uri)request.Url);
|
var requestMessage = new HttpRequestMessage(request.Method, (Uri)request.Url)
|
||||||
|
{
|
||||||
|
Version = HttpVersion.Version20,
|
||||||
|
VersionPolicy = HttpVersionPolicy.RequestVersionOrLower
|
||||||
|
};
|
||||||
requestMessage.Headers.UserAgent.ParseAdd(_userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent));
|
requestMessage.Headers.UserAgent.ParseAdd(_userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent));
|
||||||
requestMessage.Headers.ConnectionClose = !request.ConnectionKeepAlive;
|
requestMessage.Headers.ConnectionClose = !request.ConnectionKeepAlive;
|
||||||
|
|
||||||
@@ -99,7 +103,7 @@ namespace NzbDrone.Common.Http.Dispatchers
|
|||||||
|
|
||||||
var httpClient = GetClient(request.Url);
|
var httpClient = GetClient(request.Url);
|
||||||
|
|
||||||
using var responseMessage = httpClient.Send(requestMessage, HttpCompletionOption.ResponseHeadersRead, cts.Token);
|
using var responseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cts.Token);
|
||||||
{
|
{
|
||||||
byte[] data = null;
|
byte[] data = null;
|
||||||
|
|
||||||
@@ -107,11 +111,11 @@ namespace NzbDrone.Common.Http.Dispatchers
|
|||||||
{
|
{
|
||||||
if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
|
if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
|
||||||
{
|
{
|
||||||
responseMessage.Content.CopyTo(request.ResponseStream, null, cts.Token);
|
await responseMessage.Content.CopyToAsync(request.ResponseStream, null, cts.Token);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
data = responseMessage.Content.ReadAsByteArrayAsync(cts.Token).GetAwaiter().GetResult();
|
data = await responseMessage.Content.ReadAsByteArrayAsync(cts.Token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -123,7 +127,7 @@ namespace NzbDrone.Common.Http.Dispatchers
|
|||||||
|
|
||||||
headers.Add(responseMessage.Content.Headers.ToNameValueCollection());
|
headers.Add(responseMessage.Content.Headers.ToNameValueCollection());
|
||||||
|
|
||||||
return new HttpResponse(request, new HttpHeader(headers), data, responseMessage.StatusCode);
|
return new HttpResponse(request, new HttpHeader(headers), data, responseMessage.StatusCode, responseMessage.Version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +164,8 @@ namespace NzbDrone.Common.Http.Dispatchers
|
|||||||
|
|
||||||
var client = new System.Net.Http.HttpClient(handler)
|
var client = new System.Net.Http.HttpClient(handler)
|
||||||
{
|
{
|
||||||
|
DefaultRequestVersion = HttpVersion.Version20,
|
||||||
|
DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower,
|
||||||
Timeout = Timeout.InfiniteTimeSpan
|
Timeout = Timeout.InfiniteTimeSpan
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Cache;
|
using NzbDrone.Common.Cache;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
@@ -25,6 +26,16 @@ namespace NzbDrone.Common.Http
|
|||||||
HttpResponse Post(HttpRequest request);
|
HttpResponse Post(HttpRequest request);
|
||||||
HttpResponse<T> Post<T>(HttpRequest request)
|
HttpResponse<T> Post<T>(HttpRequest request)
|
||||||
where T : new();
|
where T : new();
|
||||||
|
|
||||||
|
Task<HttpResponse> ExecuteAsync(HttpRequest request);
|
||||||
|
Task DownloadFileAsync(string url, string fileName, string userAgent = null);
|
||||||
|
Task<HttpResponse> GetAsync(HttpRequest request);
|
||||||
|
Task<HttpResponse<T>> GetAsync<T>(HttpRequest request)
|
||||||
|
where T : new();
|
||||||
|
Task<HttpResponse> HeadAsync(HttpRequest request);
|
||||||
|
Task<HttpResponse> PostAsync(HttpRequest request);
|
||||||
|
Task<HttpResponse<T>> PostAsync<T>(HttpRequest request)
|
||||||
|
where T : new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HttpClient : IHttpClient
|
public class HttpClient : IHttpClient
|
||||||
@@ -52,11 +63,11 @@ namespace NzbDrone.Common.Http
|
|||||||
_cookieContainerCache = cacheManager.GetCache<CookieContainer>(typeof(HttpClient));
|
_cookieContainerCache = cacheManager.GetCache<CookieContainer>(typeof(HttpClient));
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpResponse Execute(HttpRequest request)
|
public virtual async Task<HttpResponse> ExecuteAsync(HttpRequest request)
|
||||||
{
|
{
|
||||||
var cookieContainer = InitializeRequestCookies(request);
|
var cookieContainer = InitializeRequestCookies(request);
|
||||||
|
|
||||||
var response = ExecuteRequest(request, cookieContainer);
|
var response = await ExecuteRequestAsync(request, cookieContainer);
|
||||||
|
|
||||||
if (request.AllowAutoRedirect && response.HasHttpRedirect)
|
if (request.AllowAutoRedirect && response.HasHttpRedirect)
|
||||||
{
|
{
|
||||||
@@ -82,7 +93,7 @@ namespace NzbDrone.Common.Http
|
|||||||
request.ContentSummary = null;
|
request.ContentSummary = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
response = ExecuteRequest(request, cookieContainer);
|
response = await ExecuteRequestAsync(request, cookieContainer);
|
||||||
}
|
}
|
||||||
while (response.HasHttpRedirect);
|
while (response.HasHttpRedirect);
|
||||||
}
|
}
|
||||||
@@ -112,6 +123,11 @@ namespace NzbDrone.Common.Http
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HttpResponse Execute(HttpRequest request)
|
||||||
|
{
|
||||||
|
return ExecuteAsync(request).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
private static bool RequestRequiresForceGet(HttpStatusCode statusCode, HttpMethod requestMethod)
|
private static bool RequestRequiresForceGet(HttpStatusCode statusCode, HttpMethod requestMethod)
|
||||||
{
|
{
|
||||||
return statusCode switch
|
return statusCode switch
|
||||||
@@ -122,7 +138,7 @@ namespace NzbDrone.Common.Http
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpResponse ExecuteRequest(HttpRequest request, CookieContainer cookieContainer)
|
private async Task<HttpResponse> ExecuteRequestAsync(HttpRequest request, CookieContainer cookieContainer)
|
||||||
{
|
{
|
||||||
foreach (var interceptor in _requestInterceptors)
|
foreach (var interceptor in _requestInterceptors)
|
||||||
{
|
{
|
||||||
@@ -131,14 +147,14 @@ namespace NzbDrone.Common.Http
|
|||||||
|
|
||||||
if (request.RateLimit != TimeSpan.Zero)
|
if (request.RateLimit != TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
_rateLimitService.WaitAndPulse(request.Url.Host, request.RateLimitKey, request.RateLimit);
|
await _rateLimitService.WaitAndPulseAsync(request.Url.Host, request.RateLimitKey, request.RateLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Trace(request);
|
_logger.Trace(request);
|
||||||
|
|
||||||
var stopWatch = Stopwatch.StartNew();
|
var stopWatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
var response = _httpDispatcher.GetResponse(request, cookieContainer);
|
var response = await _httpDispatcher.GetResponseAsync(request, cookieContainer);
|
||||||
|
|
||||||
HandleResponseCookies(response, cookieContainer);
|
HandleResponseCookies(response, cookieContainer);
|
||||||
|
|
||||||
@@ -246,7 +262,7 @@ namespace NzbDrone.Common.Http
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DownloadFile(string url, string fileName, string userAgent = null)
|
public async Task DownloadFileAsync(string url, string fileName, string userAgent = null)
|
||||||
{
|
{
|
||||||
var fileNamePart = fileName + ".part";
|
var fileNamePart = fileName + ".part";
|
||||||
|
|
||||||
@@ -261,12 +277,13 @@ namespace NzbDrone.Common.Http
|
|||||||
_logger.Debug("Downloading [{0}] to [{1}]", url, fileName);
|
_logger.Debug("Downloading [{0}] to [{1}]", url, fileName);
|
||||||
|
|
||||||
var stopWatch = Stopwatch.StartNew();
|
var stopWatch = Stopwatch.StartNew();
|
||||||
using (var fileStream = new FileStream(fileNamePart, FileMode.Create, FileAccess.ReadWrite))
|
await using (var fileStream = new FileStream(fileNamePart, FileMode.Create, FileAccess.ReadWrite))
|
||||||
{
|
{
|
||||||
var request = new HttpRequest(url);
|
var request = new HttpRequest(url);
|
||||||
request.AllowAutoRedirect = true;
|
request.AllowAutoRedirect = true;
|
||||||
request.ResponseStream = fileStream;
|
request.ResponseStream = fileStream;
|
||||||
var response = Get(request);
|
request.RequestTimeout = TimeSpan.FromSeconds(300);
|
||||||
|
var response = await GetAsync(request);
|
||||||
|
|
||||||
if (response.Headers.ContentType != null && response.Headers.ContentType.Contains("text/html"))
|
if (response.Headers.ContentType != null && response.Headers.ContentType.Contains("text/html"))
|
||||||
{
|
{
|
||||||
@@ -293,38 +310,71 @@ namespace NzbDrone.Common.Http
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpResponse Get(HttpRequest request)
|
public void DownloadFile(string url, string fileName, string userAgent = null)
|
||||||
|
{
|
||||||
|
// https://docs.microsoft.com/en-us/archive/msdn-magazine/2015/july/async-programming-brownfield-async-development#the-thread-pool-hack
|
||||||
|
Task.Run(() => DownloadFileAsync(url, fileName, userAgent)).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<HttpResponse> GetAsync(HttpRequest request)
|
||||||
{
|
{
|
||||||
request.Method = HttpMethod.Get;
|
request.Method = HttpMethod.Get;
|
||||||
return Execute(request);
|
return ExecuteAsync(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpResponse Get(HttpRequest request)
|
||||||
|
{
|
||||||
|
return Task.Run(() => GetAsync(request)).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<HttpResponse<T>> GetAsync<T>(HttpRequest request)
|
||||||
|
where T : new()
|
||||||
|
{
|
||||||
|
var response = await GetAsync(request);
|
||||||
|
CheckResponseContentType(response);
|
||||||
|
return new HttpResponse<T>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpResponse<T> Get<T>(HttpRequest request)
|
public HttpResponse<T> Get<T>(HttpRequest request)
|
||||||
where T : new()
|
where T : new()
|
||||||
{
|
{
|
||||||
var response = Get(request);
|
return Task.Run(() => GetAsync<T>(request)).GetAwaiter().GetResult();
|
||||||
CheckResponseContentType(response);
|
}
|
||||||
return new HttpResponse<T>(response);
|
|
||||||
|
public Task<HttpResponse> HeadAsync(HttpRequest request)
|
||||||
|
{
|
||||||
|
request.Method = HttpMethod.Head;
|
||||||
|
return ExecuteAsync(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpResponse Head(HttpRequest request)
|
public HttpResponse Head(HttpRequest request)
|
||||||
{
|
{
|
||||||
request.Method = HttpMethod.Head;
|
return Task.Run(() => HeadAsync(request)).GetAwaiter().GetResult();
|
||||||
return Execute(request);
|
}
|
||||||
|
|
||||||
|
public Task<HttpResponse> PostAsync(HttpRequest request)
|
||||||
|
{
|
||||||
|
request.Method = HttpMethod.Post;
|
||||||
|
return ExecuteAsync(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpResponse Post(HttpRequest request)
|
public HttpResponse Post(HttpRequest request)
|
||||||
{
|
{
|
||||||
request.Method = HttpMethod.Post;
|
return Task.Run(() => PostAsync(request)).GetAwaiter().GetResult();
|
||||||
return Execute(request);
|
}
|
||||||
|
|
||||||
|
public async Task<HttpResponse<T>> PostAsync<T>(HttpRequest request)
|
||||||
|
where T : new()
|
||||||
|
{
|
||||||
|
var response = await PostAsync(request);
|
||||||
|
CheckResponseContentType(response);
|
||||||
|
return new HttpResponse<T>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpResponse<T> Post<T>(HttpRequest request)
|
public HttpResponse<T> Post<T>(HttpRequest request)
|
||||||
where T : new()
|
where T : new()
|
||||||
{
|
{
|
||||||
var response = Post(request);
|
return Task.Run(() => PostAsync<T>(request)).GetAwaiter().GetResult();
|
||||||
CheckResponseContentType(response);
|
|
||||||
return new HttpResponse<T>(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckResponseContentType(HttpResponse response)
|
private void CheckResponseContentType(HttpResponse response)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ namespace NzbDrone.Common.Http
|
|||||||
Method = HttpMethod.Get;
|
Method = HttpMethod.Get;
|
||||||
Url = new HttpUri(url);
|
Url = new HttpUri(url);
|
||||||
Headers = new HttpHeader();
|
Headers = new HttpHeader();
|
||||||
|
ConnectionKeepAlive = true;
|
||||||
AllowAutoRedirect = true;
|
AllowAutoRedirect = true;
|
||||||
StoreRequestCookie = true;
|
StoreRequestCookie = true;
|
||||||
LogHttpError = true;
|
LogHttpError = true;
|
||||||
|
|||||||
@@ -9,28 +9,31 @@ namespace NzbDrone.Common.Http
|
|||||||
{
|
{
|
||||||
public class HttpResponse
|
public class HttpResponse
|
||||||
{
|
{
|
||||||
private static readonly Regex RegexSetCookie = new Regex("^(.*?)=(.*?)(?:;|$)", RegexOptions.Compiled);
|
private static readonly Regex RegexSetCookie = new ("^(.*?)=(.*?)(?:;|$)", RegexOptions.Compiled);
|
||||||
|
|
||||||
public HttpResponse(HttpRequest request, HttpHeader headers, byte[] binaryData, HttpStatusCode statusCode = HttpStatusCode.OK)
|
public HttpResponse(HttpRequest request, HttpHeader headers, byte[] binaryData, HttpStatusCode statusCode = HttpStatusCode.OK, Version version = null)
|
||||||
{
|
{
|
||||||
Request = request;
|
Request = request;
|
||||||
Headers = headers;
|
Headers = headers;
|
||||||
ResponseData = binaryData;
|
ResponseData = binaryData;
|
||||||
StatusCode = statusCode;
|
StatusCode = statusCode;
|
||||||
|
Version = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpResponse(HttpRequest request, HttpHeader headers, string content, HttpStatusCode statusCode = HttpStatusCode.OK)
|
public HttpResponse(HttpRequest request, HttpHeader headers, string content, HttpStatusCode statusCode = HttpStatusCode.OK, Version version = null)
|
||||||
{
|
{
|
||||||
Request = request;
|
Request = request;
|
||||||
Headers = headers;
|
Headers = headers;
|
||||||
ResponseData = Headers.GetEncodingFromContentType().GetBytes(content);
|
ResponseData = Headers.GetEncodingFromContentType().GetBytes(content);
|
||||||
_content = content;
|
_content = content;
|
||||||
StatusCode = statusCode;
|
StatusCode = statusCode;
|
||||||
|
Version = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpRequest Request { get; private set; }
|
public HttpRequest Request { get; private set; }
|
||||||
public HttpHeader Headers { get; private set; }
|
public HttpHeader Headers { get; private set; }
|
||||||
public HttpStatusCode StatusCode { get; private set; }
|
public HttpStatusCode StatusCode { get; private set; }
|
||||||
|
public Version Version { get; private set; }
|
||||||
public byte[] ResponseData { get; private set; }
|
public byte[] ResponseData { get; private set; }
|
||||||
|
|
||||||
private string _content;
|
private string _content;
|
||||||
@@ -84,7 +87,7 @@ namespace NzbDrone.Common.Http
|
|||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
var result = string.Format("Res: [{0}] {1}: {2}.{3} ({4} bytes)", Request.Method, Request.Url, (int)StatusCode, StatusCode, ResponseData?.Length ?? 0);
|
var result = $"Res: HTTP/{Version} [{Request.Method}] {Request.Url}: {(int)StatusCode}.{StatusCode} ({ResponseData?.Length ?? 0} bytes)";
|
||||||
|
|
||||||
if (HasHttpError && Headers.ContentType.IsNotNullOrWhiteSpace() && !Headers.ContentType.Equals("text/html", StringComparison.InvariantCultureIgnoreCase))
|
if (HasHttpError && Headers.ContentType.IsNotNullOrWhiteSpace() && !Headers.ContentType.Equals("text/html", StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
@@ -99,7 +102,7 @@ namespace NzbDrone.Common.Http
|
|||||||
where T : new()
|
where T : new()
|
||||||
{
|
{
|
||||||
public HttpResponse(HttpResponse response)
|
public HttpResponse(HttpResponse response)
|
||||||
: base(response.Request, response.Headers, response.ResponseData, response.StatusCode)
|
: base(response.Request, response.Headers, response.ResponseData, response.StatusCode, response.Version)
|
||||||
{
|
{
|
||||||
Resource = Json.Deserialize<T>(response.Content);
|
Resource = Json.Deserialize<T>(response.Content);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Common.Instrumentation
|
|||||||
new (@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
new (@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
|
|
||||||
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
|
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
|
||||||
new (@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
|
new (@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
|
|
||||||
// Path
|
// Path
|
||||||
new (@"C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
new (@"C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Cache;
|
using NzbDrone.Common.Cache;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
@@ -10,6 +11,8 @@ namespace NzbDrone.Common.TPL
|
|||||||
{
|
{
|
||||||
void WaitAndPulse(string key, TimeSpan interval);
|
void WaitAndPulse(string key, TimeSpan interval);
|
||||||
void WaitAndPulse(string key, string subKey, TimeSpan interval);
|
void WaitAndPulse(string key, string subKey, TimeSpan interval);
|
||||||
|
Task WaitAndPulseAsync(string key, TimeSpan interval);
|
||||||
|
Task WaitAndPulseAsync(string key, string subKey, TimeSpan interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RateLimitService : IRateLimitService
|
public class RateLimitService : IRateLimitService
|
||||||
@@ -28,7 +31,34 @@ namespace NzbDrone.Common.TPL
|
|||||||
WaitAndPulse(key, null, interval);
|
WaitAndPulse(key, null, interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task WaitAndPulseAsync(string key, TimeSpan interval)
|
||||||
|
{
|
||||||
|
await WaitAndPulseAsync(key, null, interval);
|
||||||
|
}
|
||||||
|
|
||||||
public void WaitAndPulse(string key, string subKey, TimeSpan interval)
|
public void WaitAndPulse(string key, string subKey, TimeSpan interval)
|
||||||
|
{
|
||||||
|
var delay = GetDelay(key, subKey, interval);
|
||||||
|
|
||||||
|
if (delay.TotalSeconds > 0.0)
|
||||||
|
{
|
||||||
|
_logger.Trace("Rate Limit triggered, delaying '{0}' for {1:0.000} sec", key, delay.TotalSeconds);
|
||||||
|
System.Threading.Thread.Sleep(delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WaitAndPulseAsync(string key, string subKey, TimeSpan interval)
|
||||||
|
{
|
||||||
|
var delay = GetDelay(key, subKey, interval);
|
||||||
|
|
||||||
|
if (delay.TotalSeconds > 0.0)
|
||||||
|
{
|
||||||
|
_logger.Trace("Rate Limit triggered, delaying '{0}' for {1:0.000} sec", key, delay.TotalSeconds);
|
||||||
|
await Task.Delay(delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TimeSpan GetDelay(string key, string subKey, TimeSpan interval)
|
||||||
{
|
{
|
||||||
var waitUntil = DateTime.UtcNow.Add(interval);
|
var waitUntil = DateTime.UtcNow.Add(interval);
|
||||||
|
|
||||||
@@ -59,13 +89,7 @@ namespace NzbDrone.Common.TPL
|
|||||||
|
|
||||||
waitUntil -= interval;
|
waitUntil -= interval;
|
||||||
|
|
||||||
var delay = waitUntil - DateTime.UtcNow;
|
return waitUntil - DateTime.UtcNow;
|
||||||
|
|
||||||
if (delay.TotalSeconds > 0.0)
|
|
||||||
{
|
|
||||||
_logger.Trace("Rate Limit triggered, delaying '{0}' for {1:0.000} sec", key, delay.TotalSeconds);
|
|
||||||
System.Threading.Thread.Sleep(delay);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using FluentAssertions;
|
|||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.Books;
|
using NzbDrone.Core.Books;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.CustomFormats;
|
using NzbDrone.Core.CustomFormats;
|
||||||
using NzbDrone.Core.DecisionEngine.Specifications;
|
using NzbDrone.Core.DecisionEngine.Specifications;
|
||||||
using NzbDrone.Core.Download.TrackedDownloads;
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
@@ -369,5 +370,31 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
|||||||
|
|
||||||
Subject.IsSatisfiedBy(_remoteBook, null).Accepted.Should().BeTrue();
|
Subject.IsSatisfiedBy(_remoteBook, null).Accepted.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_false_if_same_quality_non_proper_in_queue_and_download_propers_is_do_not_upgrade()
|
||||||
|
{
|
||||||
|
_remoteBook.ParsedBookInfo.Quality = new QualityModel(Quality.FLAC, new Revision(2));
|
||||||
|
_author.QualityProfile.Value.Cutoff = _remoteBook.ParsedBookInfo.Quality.Quality.Id;
|
||||||
|
|
||||||
|
Mocker.GetMock<IConfigService>()
|
||||||
|
.Setup(s => s.DownloadPropersAndRepacks)
|
||||||
|
.Returns(ProperDownloadTypes.DoNotUpgrade);
|
||||||
|
|
||||||
|
var remoteBook = Builder<RemoteBook>.CreateNew()
|
||||||
|
.With(r => r.Author = _author)
|
||||||
|
.With(r => r.Books = new List<Book> { _book })
|
||||||
|
.With(r => r.ParsedBookInfo = new ParsedBookInfo
|
||||||
|
{
|
||||||
|
Quality = new QualityModel(Quality.FLAC)
|
||||||
|
})
|
||||||
|
.With(r => r.Release = _releaseInfo)
|
||||||
|
.With(r => r.CustomFormats = new List<CustomFormat>())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
GivenQueue(new List<RemoteBook> { remoteBook });
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_remoteBook, null).Accepted.Should().BeFalse();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-2
@@ -1,9 +1,10 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.Books;
|
using NzbDrone.Core.Books;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.DecisionEngine.Specifications.RssSync;
|
using NzbDrone.Core.DecisionEngine.Specifications.RssSync;
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Indexers;
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
@@ -33,8 +34,13 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
|
|||||||
};
|
};
|
||||||
|
|
||||||
Mocker
|
Mocker
|
||||||
.GetMock<IIndexerRepository>()
|
.GetMock<IIndexerFactory>()
|
||||||
.Setup(m => m.Get(It.IsAny<int>()))
|
.Setup(m => m.Get(It.IsAny<int>()))
|
||||||
|
.Throws(new ModelNotFoundException(typeof(IndexerDefinition), -1));
|
||||||
|
|
||||||
|
Mocker
|
||||||
|
.GetMock<IIndexerFactory>()
|
||||||
|
.Setup(m => m.Get(1))
|
||||||
.Returns(_fakeIndexerDefinition);
|
.Returns(_fakeIndexerDefinition);
|
||||||
|
|
||||||
_specification = Mocker.Resolve<IndexerTagSpecification>();
|
_specification = Mocker.Resolve<IndexerTagSpecification>();
|
||||||
@@ -106,5 +112,25 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
|
|||||||
|
|
||||||
_specification.IsSatisfiedBy(_parseResultMulti, new BookSearchCriteria { MonitoredBooksOnly = true }).Accepted.Should().BeFalse();
|
_specification.IsSatisfiedBy(_parseResultMulti, new BookSearchCriteria { MonitoredBooksOnly = true }).Accepted.Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void release_without_indexerid_should_return_true()
|
||||||
|
{
|
||||||
|
_fakeIndexerDefinition.Tags = new HashSet<int> { 456 };
|
||||||
|
_fakeAuthor.Tags = new HashSet<int> { 123, 789 };
|
||||||
|
_fakeRelease.IndexerId = 0;
|
||||||
|
|
||||||
|
_specification.IsSatisfiedBy(_parseResultMulti, new BookSearchCriteria { MonitoredBooksOnly = true }).Accepted.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void release_with_invalid_indexerid_should_return_true()
|
||||||
|
{
|
||||||
|
_fakeIndexerDefinition.Tags = new HashSet<int> { 456 };
|
||||||
|
_fakeAuthor.Tags = new HashSet<int> { 123, 789 };
|
||||||
|
_fakeRelease.IndexerId = 2;
|
||||||
|
|
||||||
|
_specification.IsSatisfiedBy(_parseResultMulti, new BookSearchCriteria { MonitoredBooksOnly = true }).Accepted.Should().BeTrue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-26
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
@@ -58,7 +59,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_download_report_if_book_was_not_already_downloaded()
|
public async Task should_download_report_if_book_was_not_already_downloaded()
|
||||||
{
|
{
|
||||||
var books = new List<Book> { GetBook(1) };
|
var books = new List<Book> { GetBook(1) };
|
||||||
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
||||||
@@ -66,12 +67,12 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
|||||||
var decisions = new List<DownloadDecision>();
|
var decisions = new List<DownloadDecision>();
|
||||||
decisions.Add(new DownloadDecision(remoteBook));
|
decisions.Add(new DownloadDecision(remoteBook));
|
||||||
|
|
||||||
Subject.ProcessDecisions(decisions);
|
await Subject.ProcessDecisions(decisions);
|
||||||
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteBook>()), Times.Once());
|
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteBook>()), Times.Once());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_only_download_book_once()
|
public async Task should_only_download_book_once()
|
||||||
{
|
{
|
||||||
var books = new List<Book> { GetBook(1) };
|
var books = new List<Book> { GetBook(1) };
|
||||||
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
||||||
@@ -80,12 +81,12 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
|||||||
decisions.Add(new DownloadDecision(remoteBook));
|
decisions.Add(new DownloadDecision(remoteBook));
|
||||||
decisions.Add(new DownloadDecision(remoteBook));
|
decisions.Add(new DownloadDecision(remoteBook));
|
||||||
|
|
||||||
Subject.ProcessDecisions(decisions);
|
await Subject.ProcessDecisions(decisions);
|
||||||
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteBook>()), Times.Once());
|
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteBook>()), Times.Once());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_download_if_any_book_was_already_downloaded()
|
public async Task should_not_download_if_any_book_was_already_downloaded()
|
||||||
{
|
{
|
||||||
var remoteBook1 = GetRemoteBook(
|
var remoteBook1 = GetRemoteBook(
|
||||||
new List<Book> { GetBook(1) },
|
new List<Book> { GetBook(1) },
|
||||||
@@ -99,12 +100,12 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
|||||||
decisions.Add(new DownloadDecision(remoteBook1));
|
decisions.Add(new DownloadDecision(remoteBook1));
|
||||||
decisions.Add(new DownloadDecision(remoteBook2));
|
decisions.Add(new DownloadDecision(remoteBook2));
|
||||||
|
|
||||||
Subject.ProcessDecisions(decisions);
|
await Subject.ProcessDecisions(decisions);
|
||||||
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteBook>()), Times.Once());
|
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteBook>()), Times.Once());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_downloaded_reports()
|
public async Task should_return_downloaded_reports()
|
||||||
{
|
{
|
||||||
var books = new List<Book> { GetBook(1) };
|
var books = new List<Book> { GetBook(1) };
|
||||||
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
||||||
@@ -112,11 +113,13 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
|||||||
var decisions = new List<DownloadDecision>();
|
var decisions = new List<DownloadDecision>();
|
||||||
decisions.Add(new DownloadDecision(remoteBook));
|
decisions.Add(new DownloadDecision(remoteBook));
|
||||||
|
|
||||||
Subject.ProcessDecisions(decisions).Grabbed.Should().HaveCount(1);
|
var result = await Subject.ProcessDecisions(decisions);
|
||||||
|
|
||||||
|
result.Grabbed.Should().HaveCount(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_all_downloaded_reports()
|
public async Task should_return_all_downloaded_reports()
|
||||||
{
|
{
|
||||||
var remoteBook1 = GetRemoteBook(
|
var remoteBook1 = GetRemoteBook(
|
||||||
new List<Book> { GetBook(1) },
|
new List<Book> { GetBook(1) },
|
||||||
@@ -130,11 +133,13 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
|||||||
decisions.Add(new DownloadDecision(remoteBook1));
|
decisions.Add(new DownloadDecision(remoteBook1));
|
||||||
decisions.Add(new DownloadDecision(remoteBook2));
|
decisions.Add(new DownloadDecision(remoteBook2));
|
||||||
|
|
||||||
Subject.ProcessDecisions(decisions).Grabbed.Should().HaveCount(2);
|
var result = await Subject.ProcessDecisions(decisions);
|
||||||
|
|
||||||
|
result.Grabbed.Should().HaveCount(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_only_return_downloaded_reports()
|
public async Task should_only_return_downloaded_reports()
|
||||||
{
|
{
|
||||||
var remoteBook1 = GetRemoteBook(
|
var remoteBook1 = GetRemoteBook(
|
||||||
new List<Book> { GetBook(1) },
|
new List<Book> { GetBook(1) },
|
||||||
@@ -153,11 +158,13 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
|||||||
decisions.Add(new DownloadDecision(remoteBook2));
|
decisions.Add(new DownloadDecision(remoteBook2));
|
||||||
decisions.Add(new DownloadDecision(remoteBook3));
|
decisions.Add(new DownloadDecision(remoteBook3));
|
||||||
|
|
||||||
Subject.ProcessDecisions(decisions).Grabbed.Should().HaveCount(2);
|
var result = await Subject.ProcessDecisions(decisions);
|
||||||
|
|
||||||
|
result.Grabbed.Should().HaveCount(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_add_to_downloaded_list_when_download_fails()
|
public async Task should_not_add_to_downloaded_list_when_download_fails()
|
||||||
{
|
{
|
||||||
var books = new List<Book> { GetBook(1) };
|
var books = new List<Book> { GetBook(1) };
|
||||||
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
||||||
@@ -166,7 +173,11 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
|||||||
decisions.Add(new DownloadDecision(remoteBook));
|
decisions.Add(new DownloadDecision(remoteBook));
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.IsAny<RemoteBook>())).Throws(new Exception());
|
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.IsAny<RemoteBook>())).Throws(new Exception());
|
||||||
Subject.ProcessDecisions(decisions).Grabbed.Should().BeEmpty();
|
|
||||||
|
var result = await Subject.ProcessDecisions(decisions);
|
||||||
|
|
||||||
|
result.Grabbed.Should().BeEmpty();
|
||||||
|
|
||||||
ExceptionVerification.ExpectedWarns(1);
|
ExceptionVerification.ExpectedWarns(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +192,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_grab_if_pending()
|
public async Task should_not_grab_if_pending()
|
||||||
{
|
{
|
||||||
var books = new List<Book> { GetBook(1) };
|
var books = new List<Book> { GetBook(1) };
|
||||||
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
||||||
@@ -189,12 +200,12 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
|||||||
var decisions = new List<DownloadDecision>();
|
var decisions = new List<DownloadDecision>();
|
||||||
decisions.Add(new DownloadDecision(remoteBook, new Rejection("Failure!", RejectionType.Temporary)));
|
decisions.Add(new DownloadDecision(remoteBook, new Rejection("Failure!", RejectionType.Temporary)));
|
||||||
|
|
||||||
Subject.ProcessDecisions(decisions);
|
await Subject.ProcessDecisions(decisions);
|
||||||
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteBook>()), Times.Never());
|
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteBook>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_add_to_pending_if_book_was_grabbed()
|
public async Task should_not_add_to_pending_if_book_was_grabbed()
|
||||||
{
|
{
|
||||||
var books = new List<Book> { GetBook(1) };
|
var books = new List<Book> { GetBook(1) };
|
||||||
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
||||||
@@ -203,12 +214,12 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
|||||||
decisions.Add(new DownloadDecision(remoteBook));
|
decisions.Add(new DownloadDecision(remoteBook));
|
||||||
decisions.Add(new DownloadDecision(remoteBook, new Rejection("Failure!", RejectionType.Temporary)));
|
decisions.Add(new DownloadDecision(remoteBook, new Rejection("Failure!", RejectionType.Temporary)));
|
||||||
|
|
||||||
Subject.ProcessDecisions(decisions);
|
await Subject.ProcessDecisions(decisions);
|
||||||
Mocker.GetMock<IPendingReleaseService>().Verify(v => v.AddMany(It.IsAny<List<Tuple<DownloadDecision, PendingReleaseReason>>>()), Times.Never());
|
Mocker.GetMock<IPendingReleaseService>().Verify(v => v.AddMany(It.IsAny<List<Tuple<DownloadDecision, PendingReleaseReason>>>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_add_to_pending_even_if_already_added_to_pending()
|
public async Task should_add_to_pending_even_if_already_added_to_pending()
|
||||||
{
|
{
|
||||||
var books = new List<Book> { GetBook(1) };
|
var books = new List<Book> { GetBook(1) };
|
||||||
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
||||||
@@ -217,12 +228,12 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
|||||||
decisions.Add(new DownloadDecision(remoteBook, new Rejection("Failure!", RejectionType.Temporary)));
|
decisions.Add(new DownloadDecision(remoteBook, new Rejection("Failure!", RejectionType.Temporary)));
|
||||||
decisions.Add(new DownloadDecision(remoteBook, new Rejection("Failure!", RejectionType.Temporary)));
|
decisions.Add(new DownloadDecision(remoteBook, new Rejection("Failure!", RejectionType.Temporary)));
|
||||||
|
|
||||||
Subject.ProcessDecisions(decisions);
|
await Subject.ProcessDecisions(decisions);
|
||||||
Mocker.GetMock<IPendingReleaseService>().Verify(v => v.AddMany(It.IsAny<List<Tuple<DownloadDecision, PendingReleaseReason>>>()), Times.Once());
|
Mocker.GetMock<IPendingReleaseService>().Verify(v => v.AddMany(It.IsAny<List<Tuple<DownloadDecision, PendingReleaseReason>>>()), Times.Once());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_add_to_failed_if_already_failed_for_that_protocol()
|
public async Task should_add_to_failed_if_already_failed_for_that_protocol()
|
||||||
{
|
{
|
||||||
var books = new List<Book> { GetBook(1) };
|
var books = new List<Book> { GetBook(1) };
|
||||||
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
||||||
@@ -234,12 +245,12 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
|||||||
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.IsAny<RemoteBook>()))
|
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.IsAny<RemoteBook>()))
|
||||||
.Throws(new DownloadClientUnavailableException("Download client failed"));
|
.Throws(new DownloadClientUnavailableException("Download client failed"));
|
||||||
|
|
||||||
Subject.ProcessDecisions(decisions);
|
await Subject.ProcessDecisions(decisions);
|
||||||
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteBook>()), Times.Once());
|
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteBook>()), Times.Once());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_add_to_failed_if_failed_for_a_different_protocol()
|
public async Task should_not_add_to_failed_if_failed_for_a_different_protocol()
|
||||||
{
|
{
|
||||||
var books = new List<Book> { GetBook(1) };
|
var books = new List<Book> { GetBook(1) };
|
||||||
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3), DownloadProtocol.Usenet);
|
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3), DownloadProtocol.Usenet);
|
||||||
@@ -252,13 +263,13 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
|||||||
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.Is<RemoteBook>(r => r.Release.DownloadProtocol == DownloadProtocol.Usenet)))
|
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.Is<RemoteBook>(r => r.Release.DownloadProtocol == DownloadProtocol.Usenet)))
|
||||||
.Throws(new DownloadClientUnavailableException("Download client failed"));
|
.Throws(new DownloadClientUnavailableException("Download client failed"));
|
||||||
|
|
||||||
Subject.ProcessDecisions(decisions);
|
await Subject.ProcessDecisions(decisions);
|
||||||
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.Is<RemoteBook>(r => r.Release.DownloadProtocol == DownloadProtocol.Usenet)), Times.Once());
|
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.Is<RemoteBook>(r => r.Release.DownloadProtocol == DownloadProtocol.Usenet)), Times.Once());
|
||||||
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.Is<RemoteBook>(r => r.Release.DownloadProtocol == DownloadProtocol.Torrent)), Times.Once());
|
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.Is<RemoteBook>(r => r.Release.DownloadProtocol == DownloadProtocol.Torrent)), Times.Once());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_add_to_rejected_if_release_unavailable_on_indexer()
|
public async Task should_add_to_rejected_if_release_unavailable_on_indexer()
|
||||||
{
|
{
|
||||||
var books = new List<Book> { GetBook(1) };
|
var books = new List<Book> { GetBook(1) };
|
||||||
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
||||||
@@ -270,7 +281,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
|||||||
.Setup(s => s.DownloadReport(It.IsAny<RemoteBook>()))
|
.Setup(s => s.DownloadReport(It.IsAny<RemoteBook>()))
|
||||||
.Throws(new ReleaseUnavailableException(remoteBook.Release, "That 404 Error is not just a Quirk"));
|
.Throws(new ReleaseUnavailableException(remoteBook.Release, "That 404 Error is not just a Quirk"));
|
||||||
|
|
||||||
var result = Subject.ProcessDecisions(decisions);
|
var result = await Subject.ProcessDecisions(decisions);
|
||||||
|
|
||||||
result.Grabbed.Should().BeEmpty();
|
result.Grabbed.Should().BeEmpty();
|
||||||
result.Rejected.Should().NotBeEmpty();
|
result.Rejected.Should().NotBeEmpty();
|
||||||
|
|||||||
+30
-27
@@ -4,6 +4,7 @@ using System.IO;
|
|||||||
using System.IO.Abstractions;
|
using System.IO.Abstractions;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
@@ -69,7 +70,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
protected void GivenFailedDownload()
|
protected void GivenFailedDownload()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IHttpClient>()
|
Mocker.GetMock<IHttpClient>()
|
||||||
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
.Setup(s => s.GetAsync(It.IsAny<HttpRequest>()))
|
||||||
.Throws(new WebException());
|
.Throws(new WebException());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,19 +148,19 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_download_file_if_it_doesnt_exist()
|
public async Task Download_should_download_file_if_it_doesnt_exist()
|
||||||
{
|
{
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
Subject.Download(remoteBook, CreateIndexer());
|
await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
||||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Once());
|
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Once());
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_save_magnet_if_enabled()
|
public async Task Download_should_save_magnet_if_enabled()
|
||||||
{
|
{
|
||||||
GivenMagnetFilePath();
|
GivenMagnetFilePath();
|
||||||
Subject.Definition.Settings.As<TorrentBlackholeSettings>().SaveMagnetFiles = true;
|
Subject.Definition.Settings.As<TorrentBlackholeSettings>().SaveMagnetFiles = true;
|
||||||
@@ -167,16 +168,16 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
remoteBook.Release.DownloadUrl = null;
|
remoteBook.Release.DownloadUrl = null;
|
||||||
|
|
||||||
Subject.Download(remoteBook, CreateIndexer());
|
await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Never());
|
||||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Never());
|
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Never());
|
||||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_magnetFilePath), Times.Once());
|
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_magnetFilePath), Times.Once());
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_save_magnet_using_specified_extension()
|
public async Task Download_should_save_magnet_using_specified_extension()
|
||||||
{
|
{
|
||||||
var magnetFileExtension = ".url";
|
var magnetFileExtension = ".url";
|
||||||
GivenMagnetFilePath(magnetFileExtension);
|
GivenMagnetFilePath(magnetFileExtension);
|
||||||
@@ -187,12 +188,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
remoteBook.Release.DownloadUrl = null;
|
remoteBook.Release.DownloadUrl = null;
|
||||||
|
|
||||||
Subject.Download(remoteBook, CreateIndexer());
|
await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Never());
|
||||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Never());
|
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Never());
|
||||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_magnetFilePath), Times.Once());
|
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_magnetFilePath), Times.Once());
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -202,31 +203,31 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
remoteBook.Release.DownloadUrl = null;
|
remoteBook.Release.DownloadUrl = null;
|
||||||
|
|
||||||
Assert.Throws<ReleaseDownloadException>(() => Subject.Download(remoteBook, CreateIndexer()));
|
Assert.ThrowsAsync<ReleaseDownloadException>(async () => await Subject.Download(remoteBook, CreateIndexer()));
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Never());
|
||||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Never());
|
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Never());
|
||||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_magnetFilePath), Times.Never());
|
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_magnetFilePath), Times.Never());
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_prefer_torrent_over_magnet()
|
public async Task Download_should_prefer_torrent_over_magnet()
|
||||||
{
|
{
|
||||||
Subject.Definition.Settings.As<TorrentBlackholeSettings>().SaveMagnetFiles = true;
|
Subject.Definition.Settings.As<TorrentBlackholeSettings>().SaveMagnetFiles = true;
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
Subject.Download(remoteBook, CreateIndexer());
|
await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
||||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Once());
|
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Once());
|
||||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_magnetFilePath), Times.Never());
|
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_magnetFilePath), Times.Never());
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_replace_illegal_characters_in_title()
|
public async Task Download_should_replace_illegal_characters_in_title()
|
||||||
{
|
{
|
||||||
var illegalTitle = "Radiohead - Scotch Mist [2008/FLAC/Lossless]";
|
var illegalTitle = "Radiohead - Scotch Mist [2008/FLAC/Lossless]";
|
||||||
var expectedFilename = Path.Combine(_blackholeFolder, "Radiohead - Scotch Mist [2008+FLAC+Lossless]" + Path.GetExtension(_filePath));
|
var expectedFilename = Path.Combine(_blackholeFolder, "Radiohead - Scotch Mist [2008+FLAC+Lossless]" + Path.GetExtension(_filePath));
|
||||||
@@ -234,11 +235,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
remoteBook.Release.Title = illegalTitle;
|
remoteBook.Release.Title = illegalTitle;
|
||||||
|
|
||||||
Subject.Download(remoteBook, CreateIndexer());
|
await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
||||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(expectedFilename), Times.Once());
|
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(expectedFilename), Times.Once());
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -247,7 +248,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
remoteBook.Release.DownloadUrl = null;
|
remoteBook.Release.DownloadUrl = null;
|
||||||
|
|
||||||
Assert.Throws<ReleaseDownloadException>(() => Subject.Download(remoteBook, CreateIndexer()));
|
Assert.ThrowsAsync<ReleaseDownloadException>(async () => await Subject.Download(remoteBook, CreateIndexer()));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -317,11 +318,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_null_hash()
|
public async Task should_return_null_hash()
|
||||||
{
|
{
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
Subject.Download(remoteBook, CreateIndexer()).Should().BeNull();
|
var result = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
|
result.Should().BeNull();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-8
@@ -4,6 +4,7 @@ using System.IO;
|
|||||||
using System.IO.Abstractions;
|
using System.IO.Abstractions;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
@@ -119,19 +120,19 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_download_file_if_it_doesnt_exist()
|
public async Task Download_should_download_file_if_it_doesnt_exist()
|
||||||
{
|
{
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
Subject.Download(remoteBook, CreateIndexer());
|
await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
||||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Once());
|
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Once());
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_replace_illegal_characters_in_title()
|
public async Task Download_should_replace_illegal_characters_in_title()
|
||||||
{
|
{
|
||||||
var illegalTitle = "Radiohead - Scotch Mist [2008/FLAC/Lossless]";
|
var illegalTitle = "Radiohead - Scotch Mist [2008/FLAC/Lossless]";
|
||||||
var expectedFilename = Path.Combine(_blackholeFolder, "Radiohead - Scotch Mist [2008+FLAC+Lossless]" + Path.GetExtension(_filePath));
|
var expectedFilename = Path.Combine(_blackholeFolder, "Radiohead - Scotch Mist [2008+FLAC+Lossless]" + Path.GetExtension(_filePath));
|
||||||
@@ -139,11 +140,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
remoteBook.Release.Title = illegalTitle;
|
remoteBook.Release.Title = illegalTitle;
|
||||||
|
|
||||||
Subject.Download(remoteBook, CreateIndexer());
|
await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
||||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(expectedFilename), Times.Once());
|
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(expectedFilename), Times.Once());
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@@ -200,26 +201,26 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_return_unique_id()
|
public async Task Download_should_return_unique_id()
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")]
|
[TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")]
|
||||||
public void Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
public async Task Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
remoteBook.Release.DownloadUrl = magnetUrl;
|
remoteBook.Release.DownloadUrl = magnetUrl;
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().Be(expectedHash);
|
id.Should().Be(expectedHash);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NLog;
|
using NLog;
|
||||||
@@ -36,8 +37,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
|
|||||||
.Returns(() => CreateRemoteBook());
|
.Returns(() => CreateRemoteBook());
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>()
|
Mocker.GetMock<IHttpClient>()
|
||||||
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
.Setup(s => s.GetAsync(It.IsAny<HttpRequest>()))
|
||||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[0]));
|
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), Array.Empty<byte>())));
|
||||||
|
|
||||||
Mocker.GetMock<IRemotePathMappingService>()
|
Mocker.GetMock<IRemotePathMappingService>()
|
||||||
.Setup(v => v.RemapRemoteToLocal(It.IsAny<string>(), It.IsAny<OsPath>()))
|
.Setup(v => v.RemapRemoteToLocal(It.IsAny<string>(), It.IsAny<OsPath>()))
|
||||||
|
|||||||
+8
-7
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@@ -385,7 +386,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_with_TvDirectory_should_force_directory()
|
public async Task Download_with_TvDirectory_should_force_directory()
|
||||||
{
|
{
|
||||||
GivenSerialNumber();
|
GivenSerialNumber();
|
||||||
GivenTvDirectory();
|
GivenTvDirectory();
|
||||||
@@ -393,7 +394,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
@@ -402,7 +403,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_with_category_should_force_directory()
|
public async Task Download_with_category_should_force_directory()
|
||||||
{
|
{
|
||||||
GivenSerialNumber();
|
GivenSerialNumber();
|
||||||
GivenMusicCategory();
|
GivenMusicCategory();
|
||||||
@@ -410,7 +411,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
@@ -419,14 +420,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_without_TvDirectory_and_Category_should_use_default()
|
public async Task Download_without_TvDirectory_and_Category_should_use_default()
|
||||||
{
|
{
|
||||||
GivenSerialNumber();
|
GivenSerialNumber();
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
@@ -505,7 +506,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
.Setup(s => s.GetSerialNumber(_settings))
|
.Setup(s => s.GetSerialNumber(_settings))
|
||||||
.Throws(new ApplicationException("Some unknown exception, HttpException or DownloadClientException"));
|
.Throws(new ApplicationException("Some unknown exception, HttpException or DownloadClientException"));
|
||||||
|
|
||||||
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.Download(remoteBook, CreateIndexer()));
|
Assert.ThrowsAsync(Is.InstanceOf<Exception>(), async () => await Subject.Download(remoteBook, CreateIndexer()));
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadStationTaskProxy>()
|
Mocker.GetMock<IDownloadStationTaskProxy>()
|
||||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, _settings), Times.Never());
|
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, _settings), Times.Never());
|
||||||
|
|||||||
+8
-7
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@@ -262,7 +263,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_with_TvDirectory_should_force_directory()
|
public async Task Download_with_TvDirectory_should_force_directory()
|
||||||
{
|
{
|
||||||
GivenSerialNumber();
|
GivenSerialNumber();
|
||||||
GivenTvDirectory();
|
GivenTvDirectory();
|
||||||
@@ -270,7 +271,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
@@ -279,7 +280,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_with_category_should_force_directory()
|
public async Task Download_with_category_should_force_directory()
|
||||||
{
|
{
|
||||||
GivenSerialNumber();
|
GivenSerialNumber();
|
||||||
GivenMusicCategory();
|
GivenMusicCategory();
|
||||||
@@ -287,7 +288,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
@@ -296,14 +297,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_without_TvDirectory_and_Category_should_use_default()
|
public async Task Download_without_TvDirectory_and_Category_should_use_default()
|
||||||
{
|
{
|
||||||
GivenSerialNumber();
|
GivenSerialNumber();
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
@@ -382,7 +383,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
.Setup(s => s.GetSerialNumber(_settings))
|
.Setup(s => s.GetSerialNumber(_settings))
|
||||||
.Throws(new ApplicationException("Some unknown exception, HttpException or DownloadClientException"));
|
.Throws(new ApplicationException("Some unknown exception, HttpException or DownloadClientException"));
|
||||||
|
|
||||||
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.Download(remoteBook, CreateIndexer()));
|
Assert.ThrowsAsync(Is.InstanceOf<Exception>(), async () => await Subject.Download(remoteBook, CreateIndexer()));
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadStationTaskProxy>()
|
Mocker.GetMock<IDownloadStationTaskProxy>()
|
||||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, _settings), Times.Never());
|
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, _settings), Times.Never());
|
||||||
|
|||||||
+9
-8
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@@ -103,8 +104,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.HadoukenTests
|
|||||||
protected void GivenSuccessfulDownload()
|
protected void GivenSuccessfulDownload()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IHttpClient>()
|
Mocker.GetMock<IHttpClient>()
|
||||||
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
.Setup(s => s.GetAsync(It.IsAny<HttpRequest>()))
|
||||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[1000]));
|
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), new byte[1000])));
|
||||||
|
|
||||||
Mocker.GetMock<IHadoukenProxy>()
|
Mocker.GetMock<IHadoukenProxy>()
|
||||||
.Setup(s => s.AddTorrentUri(It.IsAny<HadoukenSettings>(), It.IsAny<string>()))
|
.Setup(s => s.AddTorrentUri(It.IsAny<HadoukenSettings>(), It.IsAny<string>()))
|
||||||
@@ -196,13 +197,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.HadoukenTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_return_unique_id()
|
public async Task Download_should_return_unique_id()
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
}
|
}
|
||||||
@@ -277,7 +278,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.HadoukenTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_from_magnet_link_should_return_hash_uppercase()
|
public async Task Download_from_magnet_link_should_return_hash_uppercase()
|
||||||
{
|
{
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
@@ -286,13 +287,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.HadoukenTests
|
|||||||
Mocker.GetMock<IHadoukenProxy>()
|
Mocker.GetMock<IHadoukenProxy>()
|
||||||
.Setup(v => v.AddTorrentUri(It.IsAny<HadoukenSettings>(), It.IsAny<string>()));
|
.Setup(v => v.AddTorrentUri(It.IsAny<HadoukenSettings>(), It.IsAny<string>()));
|
||||||
|
|
||||||
var result = Subject.Download(remoteBook, CreateIndexer());
|
var result = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
Assert.IsFalse(result.Any(c => char.IsLower(c)));
|
Assert.IsFalse(result.Any(c => char.IsLower(c)));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_from_torrent_file_should_return_hash_uppercase()
|
public async Task Download_from_torrent_file_should_return_hash_uppercase()
|
||||||
{
|
{
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
@@ -300,7 +301,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.HadoukenTests
|
|||||||
.Setup(v => v.AddTorrentFile(It.IsAny<HadoukenSettings>(), It.IsAny<byte[]>()))
|
.Setup(v => v.AddTorrentFile(It.IsAny<HadoukenSettings>(), It.IsAny<byte[]>()))
|
||||||
.Returns("hash");
|
.Returns("hash");
|
||||||
|
|
||||||
var result = Subject.Download(remoteBook, CreateIndexer());
|
var result = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
Assert.IsFalse(result.Any(c => char.IsLower(c)));
|
Assert.IsFalse(result.Any(c => char.IsLower(c)));
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-3
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@@ -200,13 +201,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbVortexTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_return_unique_id()
|
public async Task Download_should_return_unique_id()
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
}
|
}
|
||||||
@@ -218,7 +219,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbVortexTests
|
|||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
Assert.Throws<DownloadClientException>(() => Subject.Download(remoteBook, CreateIndexer()));
|
Assert.ThrowsAsync<DownloadClientException>(async () => await Subject.Download(remoteBook, CreateIndexer()));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
@@ -339,13 +340,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_return_unique_id()
|
public async Task Download_should_return_unique_id()
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
}
|
}
|
||||||
@@ -357,7 +358,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
|
|||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
Assert.Throws<DownloadClientRejectedReleaseException>(() => Subject.Download(remoteBook, CreateIndexer()));
|
Assert.ThrowsAsync<DownloadClientRejectedReleaseException>(async () => await Subject.Download(remoteBook, CreateIndexer()));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NLog;
|
using NLog;
|
||||||
@@ -65,15 +66,15 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
|
|||||||
|
|
||||||
private void WithFailedDownload()
|
private void WithFailedDownload()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IHttpClient>().Setup(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Throws(new WebException());
|
Mocker.GetMock<IHttpClient>().Setup(c => c.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Throws(new WebException());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_download_file_if_it_doesnt_exist()
|
public async Task should_download_file_if_it_doesnt_exist()
|
||||||
{
|
{
|
||||||
Subject.Download(_remoteBook, _indexer);
|
await Subject.Download(_remoteBook, _indexer);
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(_nzbUrl, _nzbPath, null), Times.Once());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(_nzbUrl, _nzbPath, null), Times.Once());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -81,7 +82,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
|
|||||||
{
|
{
|
||||||
WithFailedDownload();
|
WithFailedDownload();
|
||||||
|
|
||||||
Assert.Throws<WebException>(() => Subject.Download(_remoteBook, _indexer));
|
Assert.ThrowsAsync<WebException>(async () => await Subject.Download(_remoteBook, _indexer));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -90,7 +91,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
|
|||||||
_remoteBook.Release.Title = "Alien Ant Farm - Discography";
|
_remoteBook.Release.Title = "Alien Ant Farm - Discography";
|
||||||
_remoteBook.ParsedBookInfo.Discography = true;
|
_remoteBook.ParsedBookInfo.Discography = true;
|
||||||
|
|
||||||
Assert.Throws<NotSupportedException>(() => Subject.Download(_remoteBook, _indexer));
|
Assert.ThrowsAsync<NotSupportedException>(async () => await Subject.Download(_remoteBook, _indexer));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -100,15 +101,15 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_replace_illegal_characters_in_title()
|
public async Task should_replace_illegal_characters_in_title()
|
||||||
{
|
{
|
||||||
var illegalTitle = "Saturday Night Live - S38E08 - Jeremy Renner/Maroon 5 [SDTV]";
|
var illegalTitle = "Saturday Night Live - S38E08 - Jeremy Renner/Maroon 5 [SDTV]";
|
||||||
var expectedFilename = Path.Combine(_pneumaticFolder, "Saturday Night Live - S38E08 - Jeremy Renner+Maroon 5 [SDTV].nzb");
|
var expectedFilename = Path.Combine(_pneumaticFolder, "Saturday Night Live - S38E08 - Jeremy Renner+Maroon 5 [SDTV].nzb");
|
||||||
_remoteBook.Release.Title = illegalTitle;
|
_remoteBook.Release.Title = illegalTitle;
|
||||||
|
|
||||||
Subject.Download(_remoteBook, _indexer);
|
await Subject.Download(_remoteBook, _indexer);
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), expectedFilename, null), Times.Once());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(It.IsAny<string>(), expectedFilename, null), Times.Once());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-16
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@@ -404,7 +405,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
|||||||
Size = 1000,
|
Size = 1000,
|
||||||
Progress = 0.7,
|
Progress = 0.7,
|
||||||
Eta = 8640000,
|
Eta = 8640000,
|
||||||
State = "stalledDL",
|
State = "pausedUP",
|
||||||
Label = "",
|
Label = "",
|
||||||
SavePath = @"C:\Torrents".AsOsAgnostic(),
|
SavePath = @"C:\Torrents".AsOsAgnostic(),
|
||||||
ContentPath = @"C:\Torrents\Droned.S01.12".AsOsAgnostic()
|
ContentPath = @"C:\Torrents\Droned.S01.12".AsOsAgnostic()
|
||||||
@@ -449,26 +450,26 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_return_unique_id()
|
public async Task Download_should_return_unique_id()
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")]
|
[TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")]
|
||||||
public void Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
public async Task Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
remoteBook.Release.DownloadUrl = magnetUrl;
|
remoteBook.Release.DownloadUrl = magnetUrl;
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().Be(expectedHash);
|
id.Should().Be(expectedHash);
|
||||||
}
|
}
|
||||||
@@ -483,7 +484,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
|||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
remoteBook.Release.DownloadUrl = "magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR";
|
remoteBook.Release.DownloadUrl = "magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR";
|
||||||
|
|
||||||
Assert.Throws<ReleaseDownloadException>(() => Subject.Download(remoteBook, CreateIndexer()));
|
Assert.ThrowsAsync<ReleaseDownloadException>(async () => await Subject.Download(remoteBook, CreateIndexer()));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -496,28 +497,28 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
|||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
remoteBook.Release.DownloadUrl = "magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp://abc";
|
remoteBook.Release.DownloadUrl = "magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp://abc";
|
||||||
|
|
||||||
Assert.DoesNotThrow(() => Subject.Download(remoteBook, CreateIndexer()));
|
Assert.DoesNotThrowAsync(async () => await Subject.Download(remoteBook, CreateIndexer()));
|
||||||
|
|
||||||
Mocker.GetMock<IQBittorrentProxy>()
|
Mocker.GetMock<IQBittorrentProxy>()
|
||||||
.Verify(s => s.AddTorrentFromUrl(It.IsAny<string>(), It.IsAny<TorrentSeedConfiguration>(), It.IsAny<QBittorrentSettings>()), Times.Once());
|
.Verify(s => s.AddTorrentFromUrl(It.IsAny<string>(), It.IsAny<TorrentSeedConfiguration>(), It.IsAny<QBittorrentSettings>()), Times.Once());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_set_top_priority()
|
public async Task Download_should_set_top_priority()
|
||||||
{
|
{
|
||||||
GivenHighPriority();
|
GivenHighPriority();
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
Mocker.GetMock<IQBittorrentProxy>()
|
Mocker.GetMock<IQBittorrentProxy>()
|
||||||
.Verify(v => v.MoveTorrentToTopInQueue(It.IsAny<string>(), It.IsAny<QBittorrentSettings>()), Times.Once());
|
.Verify(v => v.MoveTorrentToTopInQueue(It.IsAny<string>(), It.IsAny<QBittorrentSettings>()), Times.Once());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_not_fail_if_top_priority_not_available()
|
public async Task Download_should_not_fail_if_top_priority_not_available()
|
||||||
{
|
{
|
||||||
GivenHighPriority();
|
GivenHighPriority();
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
@@ -528,7 +529,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
|||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
@@ -555,27 +556,27 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_handle_http_redirect_to_magnet()
|
public async Task Download_should_handle_http_redirect_to_magnet()
|
||||||
{
|
{
|
||||||
GivenRedirectToMagnet();
|
GivenRedirectToMagnet();
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_handle_http_redirect_to_torrent()
|
public async Task Download_should_handle_http_redirect_to_torrent()
|
||||||
{
|
{
|
||||||
GivenRedirectToTorrent();
|
GivenRedirectToTorrent();
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
}
|
}
|
||||||
@@ -633,7 +634,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
|||||||
.Returns(new QBittorrentTorrentProperties
|
.Returns(new QBittorrentTorrentProperties
|
||||||
{
|
{
|
||||||
Hash = "HASH",
|
Hash = "HASH",
|
||||||
SeedingTime = seedingTime
|
SeedingTime = seedingTime * 60
|
||||||
});
|
});
|
||||||
|
|
||||||
return torrent;
|
return torrent;
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@@ -121,13 +122,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.RTorrentTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_return_unique_id()
|
public async Task Download_should_return_unique_id()
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
@@ -300,27 +301,27 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("[ TOWN ]-[ http://www.town.ag ]-[ ANIME ]-[Usenet Provider >> http://www.ssl- <<] - [Commie] Aldnoah Zero 18 [234C8FC7]", "[ TOWN ]-[ http-++www.town.ag ]-[ ANIME ]-[Usenet Provider http-++www.ssl- ] - [Commie] Aldnoah Zero 18 [234C8FC7].nzb")]
|
[TestCase("[ TOWN ]-[ http://www.town.ag ]-[ ANIME ]-[Usenet Provider >> http://www.ssl- <<] - [Commie] Aldnoah Zero 18 [234C8FC7]", "[ TOWN ]-[ http-++www.town.ag ]-[ ANIME ]-[Usenet Provider http-++www.ssl- ] - [Commie] Aldnoah Zero 18 [234C8FC7].nzb")]
|
||||||
public void Download_should_use_clean_title(string title, string filename)
|
public async Task Download_should_use_clean_title(string title, string filename)
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
remoteBook.Release.Title = title;
|
remoteBook.Release.Title = title;
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
Mocker.GetMock<ISabnzbdProxy>()
|
Mocker.GetMock<ISabnzbdProxy>()
|
||||||
.Verify(v => v.DownloadNzb(It.IsAny<byte[]>(), filename, It.IsAny<string>(), It.IsAny<int>(), It.IsAny<SabnzbdSettings>()), Times.Once());
|
.Verify(v => v.DownloadNzb(It.IsAny<byte[]>(), filename, It.IsAny<string>(), It.IsAny<int>(), It.IsAny<SabnzbdSettings>()), Times.Once());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_return_unique_id()
|
public async Task Download_should_return_unique_id()
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
}
|
}
|
||||||
@@ -353,7 +354,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_use_sabRecentTvPriority_when_recentEpisode_is_true()
|
public async Task Download_should_use_sabRecentTvPriority_when_recentEpisode_is_true()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<ISabnzbdProxy>()
|
Mocker.GetMock<ISabnzbdProxy>()
|
||||||
.Setup(s => s.DownloadNzb(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), (int)SabnzbdPriority.High, It.IsAny<SabnzbdSettings>()))
|
.Setup(s => s.DownloadNzb(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), (int)SabnzbdPriority.High, It.IsAny<SabnzbdSettings>()))
|
||||||
@@ -366,7 +367,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
|||||||
.Build()
|
.Build()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
Subject.Download(remoteBook, CreateIndexer());
|
await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
Mocker.GetMock<ISabnzbdProxy>()
|
Mocker.GetMock<ISabnzbdProxy>()
|
||||||
.Verify(v => v.DownloadNzb(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), (int)SabnzbdPriority.High, It.IsAny<SabnzbdSettings>()), Times.Once());
|
.Verify(v => v.DownloadNzb(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), (int)SabnzbdPriority.High, It.IsAny<SabnzbdSettings>()), Times.Once());
|
||||||
@@ -451,6 +452,30 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
|||||||
result.OutputRootFolders.First().Should().Be(fullCategoryDir);
|
result.OutputRootFolders.First().Should().Be(fullCategoryDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase("0")]
|
||||||
|
[TestCase("15d")]
|
||||||
|
public void should_set_history_removes_completed_downloads_false(string historyRetention)
|
||||||
|
{
|
||||||
|
_config.Misc.history_retention = historyRetention;
|
||||||
|
|
||||||
|
var downloadClientInfo = Subject.GetStatus();
|
||||||
|
|
||||||
|
downloadClientInfo.RemovesCompletedDownloads.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("-1")]
|
||||||
|
[TestCase("15")]
|
||||||
|
[TestCase("3")]
|
||||||
|
[TestCase("3d")]
|
||||||
|
public void should_set_history_removes_completed_downloads_true(string historyRetention)
|
||||||
|
{
|
||||||
|
_config.Misc.history_retention = historyRetention;
|
||||||
|
|
||||||
|
var downloadClientInfo = Subject.GetStatus();
|
||||||
|
|
||||||
|
downloadClientInfo.RemovesCompletedDownloads.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
[TestCase(@"Y:\nzbget\root", @"completed\downloads", @"vv", @"Y:\nzbget\root\completed\downloads", @"Y:\nzbget\root\completed\downloads\vv")]
|
[TestCase(@"Y:\nzbget\root", @"completed\downloads", @"vv", @"Y:\nzbget\root\completed\downloads", @"Y:\nzbget\root\completed\downloads\vv")]
|
||||||
[TestCase(@"Y:\nzbget\root", @"completed", @"vv", @"Y:\nzbget\root\completed", @"Y:\nzbget\root\completed\vv")]
|
[TestCase(@"Y:\nzbget\root", @"completed", @"vv", @"Y:\nzbget\root\completed", @"Y:\nzbget\root\completed\vv")]
|
||||||
[TestCase(@"/nzbget/root", @"completed/downloads", @"vv", @"/nzbget/root/completed/downloads", @"/nzbget/root/completed/downloads/vv")]
|
[TestCase(@"/nzbget/root", @"completed/downloads", @"vv", @"/nzbget/root/completed/downloads", @"/nzbget/root/completed/downloads/vv")]
|
||||||
|
|||||||
+13
-12
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@@ -55,26 +56,26 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_return_unique_id()
|
public async Task Download_should_return_unique_id()
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_with_TvDirectory_should_force_directory()
|
public async Task Download_with_TvDirectory_should_force_directory()
|
||||||
{
|
{
|
||||||
GivenTvDirectory();
|
GivenTvDirectory();
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
@@ -83,14 +84,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_with_category_should_force_directory()
|
public async Task Download_with_category_should_force_directory()
|
||||||
{
|
{
|
||||||
GivenMusicCategory();
|
GivenMusicCategory();
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
@@ -99,7 +100,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_with_category_should_not_have_double_slashes()
|
public async Task Download_with_category_should_not_have_double_slashes()
|
||||||
{
|
{
|
||||||
GivenMusicCategory();
|
GivenMusicCategory();
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
@@ -108,7 +109,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
|||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
@@ -117,13 +118,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_without_TvDirectory_and_Category_should_use_default()
|
public async Task Download_without_TvDirectory_and_Category_should_use_default()
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
@@ -132,14 +133,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")]
|
[TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")]
|
||||||
public void Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
public async Task Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
remoteBook.Release.DownloadUrl = magnetUrl;
|
remoteBook.Release.DownloadUrl = magnetUrl;
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().Be(expectedHash);
|
id.Should().Be(expectedHash);
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-8
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@@ -228,13 +229,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_return_unique_id()
|
public async Task Download_should_return_unique_id()
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
}
|
}
|
||||||
@@ -252,14 +253,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests
|
|||||||
|
|
||||||
// Proxy.GetTorrents does not return original url. So item has to be found via magnet url.
|
// Proxy.GetTorrents does not return original url. So item has to be found via magnet url.
|
||||||
[TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")]
|
[TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")]
|
||||||
public void Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
public async Task Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
remoteBook.Release.DownloadUrl = magnetUrl;
|
remoteBook.Release.DownloadUrl = magnetUrl;
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().Be(expectedHash);
|
id.Should().Be(expectedHash);
|
||||||
}
|
}
|
||||||
@@ -350,27 +351,27 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_handle_http_redirect_to_magnet()
|
public async Task Download_should_handle_http_redirect_to_magnet()
|
||||||
{
|
{
|
||||||
GivenRedirectToMagnet();
|
GivenRedirectToMagnet();
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_handle_http_redirect_to_torrent()
|
public async Task Download_should_handle_http_redirect_to_torrent()
|
||||||
{
|
{
|
||||||
GivenRedirectToTorrent();
|
GivenRedirectToTorrent();
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@@ -63,26 +64,26 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_should_return_unique_id()
|
public async Task Download_should_return_unique_id()
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_with_TvDirectory_should_force_directory()
|
public async Task Download_with_TvDirectory_should_force_directory()
|
||||||
{
|
{
|
||||||
GivenTvDirectory();
|
GivenTvDirectory();
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
@@ -91,14 +92,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_with_category_should_force_directory()
|
public async Task Download_with_category_should_force_directory()
|
||||||
{
|
{
|
||||||
GivenMusicCategory();
|
GivenMusicCategory();
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
@@ -107,7 +108,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_with_category_should_not_have_double_slashes()
|
public async Task Download_with_category_should_not_have_double_slashes()
|
||||||
{
|
{
|
||||||
GivenMusicCategory();
|
GivenMusicCategory();
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
@@ -116,7 +117,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
|
|||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
@@ -125,13 +126,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_without_TvDirectory_and_Category_should_use_default()
|
public async Task Download_without_TvDirectory_and_Category_should_use_default()
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
@@ -140,14 +141,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")]
|
[TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")]
|
||||||
public void Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
public async Task Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
remoteBook.Release.DownloadUrl = magnetUrl;
|
remoteBook.Release.DownloadUrl = magnetUrl;
|
||||||
|
|
||||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().Be(expectedHash);
|
id.Should().Be(expectedHash);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@@ -77,23 +78,23 @@ namespace NzbDrone.Core.Test.Download
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_report_should_publish_on_grab_event()
|
public async Task Download_report_should_publish_on_grab_event()
|
||||||
{
|
{
|
||||||
var mock = WithUsenetClient();
|
var mock = WithUsenetClient();
|
||||||
mock.Setup(s => s.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()));
|
mock.Setup(s => s.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()));
|
||||||
|
|
||||||
Subject.DownloadReport(_parseResult);
|
await Subject.DownloadReport(_parseResult);
|
||||||
|
|
||||||
VerifyEventPublished<BookGrabbedEvent>();
|
VerifyEventPublished<BookGrabbedEvent>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_report_should_grab_using_client()
|
public async Task Download_report_should_grab_using_client()
|
||||||
{
|
{
|
||||||
var mock = WithUsenetClient();
|
var mock = WithUsenetClient();
|
||||||
mock.Setup(s => s.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()));
|
mock.Setup(s => s.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()));
|
||||||
|
|
||||||
Subject.DownloadReport(_parseResult);
|
await Subject.DownloadReport(_parseResult);
|
||||||
|
|
||||||
mock.Verify(s => s.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()), Times.Once());
|
mock.Verify(s => s.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()), Times.Once());
|
||||||
}
|
}
|
||||||
@@ -105,7 +106,7 @@ namespace NzbDrone.Core.Test.Download
|
|||||||
mock.Setup(s => s.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()))
|
mock.Setup(s => s.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()))
|
||||||
.Throws(new WebException());
|
.Throws(new WebException());
|
||||||
|
|
||||||
Assert.Throws<WebException>(() => Subject.DownloadReport(_parseResult));
|
Assert.ThrowsAsync<WebException>(async () => await Subject.DownloadReport(_parseResult));
|
||||||
|
|
||||||
VerifyEventNotPublished<BookGrabbedEvent>();
|
VerifyEventNotPublished<BookGrabbedEvent>();
|
||||||
}
|
}
|
||||||
@@ -120,7 +121,7 @@ namespace NzbDrone.Core.Test.Download
|
|||||||
throw new ReleaseDownloadException(v.Release, "Error", new WebException());
|
throw new ReleaseDownloadException(v.Release, "Error", new WebException());
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.Throws<ReleaseDownloadException>(() => Subject.DownloadReport(_parseResult));
|
Assert.ThrowsAsync<ReleaseDownloadException>(async () => await Subject.DownloadReport(_parseResult));
|
||||||
|
|
||||||
Mocker.GetMock<IIndexerStatusService>()
|
Mocker.GetMock<IIndexerStatusService>()
|
||||||
.Verify(v => v.RecordFailure(It.IsAny<int>(), It.IsAny<TimeSpan>()), Times.Once());
|
.Verify(v => v.RecordFailure(It.IsAny<int>(), It.IsAny<TimeSpan>()), Times.Once());
|
||||||
@@ -140,7 +141,7 @@ namespace NzbDrone.Core.Test.Download
|
|||||||
throw new ReleaseDownloadException(v.Release, "Error", new TooManyRequestsException(request, response));
|
throw new ReleaseDownloadException(v.Release, "Error", new TooManyRequestsException(request, response));
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.Throws<ReleaseDownloadException>(() => Subject.DownloadReport(_parseResult));
|
Assert.ThrowsAsync<ReleaseDownloadException>(async () => await Subject.DownloadReport(_parseResult));
|
||||||
|
|
||||||
Mocker.GetMock<IIndexerStatusService>()
|
Mocker.GetMock<IIndexerStatusService>()
|
||||||
.Verify(v => v.RecordFailure(It.IsAny<int>(), TimeSpan.FromMinutes(5.0)), Times.Once());
|
.Verify(v => v.RecordFailure(It.IsAny<int>(), TimeSpan.FromMinutes(5.0)), Times.Once());
|
||||||
@@ -160,7 +161,7 @@ namespace NzbDrone.Core.Test.Download
|
|||||||
throw new ReleaseDownloadException(v.Release, "Error", new TooManyRequestsException(request, response));
|
throw new ReleaseDownloadException(v.Release, "Error", new TooManyRequestsException(request, response));
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.Throws<ReleaseDownloadException>(() => Subject.DownloadReport(_parseResult));
|
Assert.ThrowsAsync<ReleaseDownloadException>(async () => await Subject.DownloadReport(_parseResult));
|
||||||
|
|
||||||
Mocker.GetMock<IIndexerStatusService>()
|
Mocker.GetMock<IIndexerStatusService>()
|
||||||
.Verify(v => v.RecordFailure(It.IsAny<int>(),
|
.Verify(v => v.RecordFailure(It.IsAny<int>(),
|
||||||
@@ -174,7 +175,7 @@ namespace NzbDrone.Core.Test.Download
|
|||||||
mock.Setup(s => s.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()))
|
mock.Setup(s => s.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()))
|
||||||
.Throws(new DownloadClientException("Some Error"));
|
.Throws(new DownloadClientException("Some Error"));
|
||||||
|
|
||||||
Assert.Throws<DownloadClientException>(() => Subject.DownloadReport(_parseResult));
|
Assert.ThrowsAsync<DownloadClientException>(async () => await Subject.DownloadReport(_parseResult));
|
||||||
|
|
||||||
Mocker.GetMock<IIndexerStatusService>()
|
Mocker.GetMock<IIndexerStatusService>()
|
||||||
.Verify(v => v.RecordFailure(It.IsAny<int>(), It.IsAny<TimeSpan>()), Times.Never());
|
.Verify(v => v.RecordFailure(It.IsAny<int>(), It.IsAny<TimeSpan>()), Times.Never());
|
||||||
@@ -190,7 +191,7 @@ namespace NzbDrone.Core.Test.Download
|
|||||||
throw new ReleaseUnavailableException(v.Release, "Error", new WebException());
|
throw new ReleaseUnavailableException(v.Release, "Error", new WebException());
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.Throws<ReleaseUnavailableException>(() => Subject.DownloadReport(_parseResult));
|
Assert.ThrowsAsync<ReleaseUnavailableException>(async () => await Subject.DownloadReport(_parseResult));
|
||||||
|
|
||||||
Mocker.GetMock<IIndexerStatusService>()
|
Mocker.GetMock<IIndexerStatusService>()
|
||||||
.Verify(v => v.RecordFailure(It.IsAny<int>(), It.IsAny<TimeSpan>()), Times.Never());
|
.Verify(v => v.RecordFailure(It.IsAny<int>(), It.IsAny<TimeSpan>()), Times.Never());
|
||||||
@@ -199,14 +200,14 @@ namespace NzbDrone.Core.Test.Download
|
|||||||
[Test]
|
[Test]
|
||||||
public void should_not_attempt_download_if_client_isnt_configured()
|
public void should_not_attempt_download_if_client_isnt_configured()
|
||||||
{
|
{
|
||||||
Assert.Throws<DownloadClientUnavailableException>(() => Subject.DownloadReport(_parseResult));
|
Assert.ThrowsAsync<DownloadClientUnavailableException>(async () => await Subject.DownloadReport(_parseResult));
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadClient>().Verify(c => c.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()), Times.Never());
|
Mocker.GetMock<IDownloadClient>().Verify(c => c.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()), Times.Never());
|
||||||
VerifyEventNotPublished<BookGrabbedEvent>();
|
VerifyEventNotPublished<BookGrabbedEvent>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_attempt_download_even_if_client_is_disabled()
|
public async Task should_attempt_download_even_if_client_is_disabled()
|
||||||
{
|
{
|
||||||
var mockUsenet = WithUsenetClient();
|
var mockUsenet = WithUsenetClient();
|
||||||
|
|
||||||
@@ -221,7 +222,7 @@ namespace NzbDrone.Core.Test.Download
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Subject.DownloadReport(_parseResult);
|
await Subject.DownloadReport(_parseResult);
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadClientStatusService>().Verify(c => c.GetBlockedProviders(), Times.Never());
|
Mocker.GetMock<IDownloadClientStatusService>().Verify(c => c.GetBlockedProviders(), Times.Never());
|
||||||
mockUsenet.Verify(c => c.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()), Times.Once());
|
mockUsenet.Verify(c => c.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()), Times.Once());
|
||||||
@@ -229,26 +230,26 @@ namespace NzbDrone.Core.Test.Download
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_send_download_to_correct_usenet_client()
|
public async Task should_send_download_to_correct_usenet_client()
|
||||||
{
|
{
|
||||||
var mockTorrent = WithTorrentClient();
|
var mockTorrent = WithTorrentClient();
|
||||||
var mockUsenet = WithUsenetClient();
|
var mockUsenet = WithUsenetClient();
|
||||||
|
|
||||||
Subject.DownloadReport(_parseResult);
|
await Subject.DownloadReport(_parseResult);
|
||||||
|
|
||||||
mockTorrent.Verify(c => c.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()), Times.Never());
|
mockTorrent.Verify(c => c.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()), Times.Never());
|
||||||
mockUsenet.Verify(c => c.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()), Times.Once());
|
mockUsenet.Verify(c => c.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()), Times.Once());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_send_download_to_correct_torrent_client()
|
public async Task should_send_download_to_correct_torrent_client()
|
||||||
{
|
{
|
||||||
var mockTorrent = WithTorrentClient();
|
var mockTorrent = WithTorrentClient();
|
||||||
var mockUsenet = WithUsenetClient();
|
var mockUsenet = WithUsenetClient();
|
||||||
|
|
||||||
_parseResult.Release.DownloadProtocol = DownloadProtocol.Torrent;
|
_parseResult.Release.DownloadProtocol = DownloadProtocol.Torrent;
|
||||||
|
|
||||||
Subject.DownloadReport(_parseResult);
|
await Subject.DownloadReport(_parseResult);
|
||||||
|
|
||||||
mockTorrent.Verify(c => c.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()), Times.Once());
|
mockTorrent.Verify(c => c.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()), Times.Once());
|
||||||
mockUsenet.Verify(c => c.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()), Times.Never());
|
mockUsenet.Verify(c => c.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()), Times.Never());
|
||||||
|
|||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
using System;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.Download.Clients;
|
||||||
|
using NzbDrone.Core.HealthCheck.Checks;
|
||||||
|
using NzbDrone.Core.Localization;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class DownloadClientRemovesCompletedDownloadsCheckFixture : CoreTest<DownloadClientRemovesCompletedDownloadsCheck>
|
||||||
|
{
|
||||||
|
private DownloadClientInfo _clientStatus;
|
||||||
|
private Mock<IDownloadClient> _downloadClient;
|
||||||
|
|
||||||
|
private static Exception[] DownloadClientExceptions =
|
||||||
|
{
|
||||||
|
new DownloadClientUnavailableException("error"),
|
||||||
|
new DownloadClientAuthenticationException("error"),
|
||||||
|
new DownloadClientException("error")
|
||||||
|
};
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_clientStatus = new DownloadClientInfo
|
||||||
|
{
|
||||||
|
IsLocalhost = true,
|
||||||
|
|
||||||
|
// SortingMode = null,
|
||||||
|
RemovesCompletedDownloads = true
|
||||||
|
};
|
||||||
|
|
||||||
|
_downloadClient = Mocker.GetMock<IDownloadClient>();
|
||||||
|
_downloadClient.Setup(s => s.Definition)
|
||||||
|
.Returns(new DownloadClientDefinition { Name = "Test" });
|
||||||
|
|
||||||
|
_downloadClient.Setup(s => s.GetStatus())
|
||||||
|
.Returns(_clientStatus);
|
||||||
|
|
||||||
|
Mocker.GetMock<IProvideDownloadClient>()
|
||||||
|
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
|
||||||
|
.Returns(new IDownloadClient[] { _downloadClient.Object });
|
||||||
|
|
||||||
|
Mocker.GetMock<ILocalizationService>()
|
||||||
|
.Setup(s => s.GetLocalizedString(It.IsAny<string>()))
|
||||||
|
.Returns("Some Warning Message");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_warning_if_removing_completed_downloads_is_enabled()
|
||||||
|
{
|
||||||
|
Subject.Check().ShouldBeWarning();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_ok_if_remove_completed_downloads_is_not_enabled()
|
||||||
|
{
|
||||||
|
_clientStatus.RemovesCompletedDownloads = false;
|
||||||
|
Subject.Check().ShouldBeOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCaseSource(nameof(DownloadClientExceptions))]
|
||||||
|
public void should_return_ok_if_client_throws_downloadclientexception(Exception ex)
|
||||||
|
{
|
||||||
|
_downloadClient.Setup(s => s.GetStatus())
|
||||||
|
.Throws(ex);
|
||||||
|
|
||||||
|
Subject.Check().ShouldBeOk();
|
||||||
|
|
||||||
|
ExceptionVerification.ExpectedErrors(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,8 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||||||
|
|
||||||
_importListReports = new List<ImportListItemInfo> { importListItem1 };
|
_importListReports = new List<ImportListItemInfo> { importListItem1 };
|
||||||
|
|
||||||
|
var mockImportList = new Mock<IImportList>();
|
||||||
|
|
||||||
Mocker.GetMock<IFetchAndParseImportList>()
|
Mocker.GetMock<IFetchAndParseImportList>()
|
||||||
.Setup(v => v.Fetch())
|
.Setup(v => v.Fetch())
|
||||||
.Returns(_importListReports);
|
.Returns(_importListReports);
|
||||||
@@ -53,6 +55,10 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||||||
.Setup(v => v.Get(It.IsAny<int>()))
|
.Setup(v => v.Get(It.IsAny<int>()))
|
||||||
.Returns(new ImportListDefinition { ShouldMonitor = ImportListMonitorType.SpecificBook });
|
.Returns(new ImportListDefinition { ShouldMonitor = ImportListMonitorType.SpecificBook });
|
||||||
|
|
||||||
|
Mocker.GetMock<IImportListFactory>()
|
||||||
|
.Setup(v => v.AutomaticAddEnabled(It.IsAny<bool>()))
|
||||||
|
.Returns(new List<IImportList> { mockImportList.Object });
|
||||||
|
|
||||||
Mocker.GetMock<IFetchAndParseImportList>()
|
Mocker.GetMock<IFetchAndParseImportList>()
|
||||||
.Setup(v => v.Fetch())
|
.Setup(v => v.Fetch())
|
||||||
.Returns(_importListReports);
|
.Returns(_importListReports);
|
||||||
@@ -322,5 +328,31 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||||||
t.First().AddOptions.BooksToMonitor.Count == expectedBooksMonitored &&
|
t.First().AddOptions.BooksToMonitor.Count == expectedBooksMonitored &&
|
||||||
t.First().Monitored == expectedAuthorMonitored), false));
|
t.First().Monitored == expectedAuthorMonitored), false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_fetch_if_no_lists_are_enabled()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IImportListFactory>()
|
||||||
|
.Setup(v => v.AutomaticAddEnabled(It.IsAny<bool>()))
|
||||||
|
.Returns(new List<IImportList>());
|
||||||
|
|
||||||
|
Subject.Execute(new ImportListSyncCommand());
|
||||||
|
|
||||||
|
Mocker.GetMock<IFetchAndParseImportList>()
|
||||||
|
.Verify(v => v.Fetch(), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_process_if_no_items_are_returned()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IFetchAndParseImportList>()
|
||||||
|
.Setup(v => v.Fetch())
|
||||||
|
.Returns(new List<ImportListItemInfo>());
|
||||||
|
|
||||||
|
Subject.Execute(new ImportListSyncCommand());
|
||||||
|
|
||||||
|
Mocker.GetMock<IImportListExclusionService>()
|
||||||
|
.Verify(v => v.All(), Times.Never);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.Books;
|
using NzbDrone.Core.Books;
|
||||||
@@ -27,11 +28,11 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
|||||||
|
|
||||||
Mocker.GetMock<ISearchForReleases>()
|
Mocker.GetMock<ISearchForReleases>()
|
||||||
.Setup(s => s.AuthorSearch(_author.Id, false, true, false))
|
.Setup(s => s.AuthorSearch(_author.Id, false, true, false))
|
||||||
.Returns(new List<DownloadDecision>());
|
.Returns(Task.FromResult(new List<DownloadDecision>()));
|
||||||
|
|
||||||
Mocker.GetMock<IProcessDownloadDecisions>()
|
Mocker.GetMock<IProcessDownloadDecisions>()
|
||||||
.Setup(s => s.ProcessDecisions(It.IsAny<List<DownloadDecision>>()))
|
.Setup(s => s.ProcessDecisions(It.IsAny<List<DownloadDecision>>()))
|
||||||
.Returns(new ProcessedDecisions(new List<DownloadDecision>(), new List<DownloadDecision>(), new List<DownloadDecision>()));
|
.Returns(Task.FromResult(new ProcessedDecisions(new List<DownloadDecision>(), new List<DownloadDecision>(), new List<DownloadDecision>())));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
@@ -60,13 +61,13 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
|||||||
|
|
||||||
_mockIndexer.Setup(v => v.Fetch(It.IsAny<BookSearchCriteria>()))
|
_mockIndexer.Setup(v => v.Fetch(It.IsAny<BookSearchCriteria>()))
|
||||||
.Callback<BookSearchCriteria>(s => result.Add(s))
|
.Callback<BookSearchCriteria>(s => result.Add(s))
|
||||||
.Returns(new List<Parser.Model.ReleaseInfo>());
|
.Returns(Task.FromResult<IList<Parser.Model.ReleaseInfo>>(new List<Parser.Model.ReleaseInfo>()));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Tags_IndexerTags_AuthorNoTags_IndexerNotIncluded()
|
public async Task Tags_IndexerTags_AuthorNoTags_IndexerNotIncluded()
|
||||||
{
|
{
|
||||||
_mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition
|
_mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition
|
||||||
{
|
{
|
||||||
@@ -76,7 +77,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
|||||||
|
|
||||||
var allCriteria = WatchForSearchCriteria();
|
var allCriteria = WatchForSearchCriteria();
|
||||||
|
|
||||||
Subject.BookSearch(_firstBook, false, true, false);
|
await Subject.BookSearch(_firstBook, false, true, false);
|
||||||
|
|
||||||
var criteria = allCriteria.OfType<BookSearchCriteria>().ToList();
|
var criteria = allCriteria.OfType<BookSearchCriteria>().ToList();
|
||||||
|
|
||||||
@@ -84,7 +85,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Tags_IndexerNoTags_AuthorTags_IndexerIncluded()
|
public async Task Tags_IndexerNoTags_AuthorTags_IndexerIncluded()
|
||||||
{
|
{
|
||||||
_mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition
|
_mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition
|
||||||
{
|
{
|
||||||
@@ -102,7 +103,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
|||||||
|
|
||||||
var allCriteria = WatchForSearchCriteria();
|
var allCriteria = WatchForSearchCriteria();
|
||||||
|
|
||||||
Subject.BookSearch(_firstBook, false, true, false);
|
await Subject.BookSearch(_firstBook, false, true, false);
|
||||||
|
|
||||||
var criteria = allCriteria.OfType<BookSearchCriteria>().ToList();
|
var criteria = allCriteria.OfType<BookSearchCriteria>().ToList();
|
||||||
|
|
||||||
@@ -110,7 +111,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Tags_IndexerAndAuthorTagsMatch_IndexerIncluded()
|
public async Task Tags_IndexerAndAuthorTagsMatch_IndexerIncluded()
|
||||||
{
|
{
|
||||||
_mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition
|
_mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition
|
||||||
{
|
{
|
||||||
@@ -129,7 +130,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
|||||||
|
|
||||||
var allCriteria = WatchForSearchCriteria();
|
var allCriteria = WatchForSearchCriteria();
|
||||||
|
|
||||||
Subject.BookSearch(_firstBook, false, true, false);
|
await Subject.BookSearch(_firstBook, false, true, false);
|
||||||
|
|
||||||
var criteria = allCriteria.OfType<BookSearchCriteria>().ToList();
|
var criteria = allCriteria.OfType<BookSearchCriteria>().ToList();
|
||||||
|
|
||||||
@@ -137,7 +138,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Tags_IndexerAndAuthorTagsMismatch_IndexerNotIncluded()
|
public async Task Tags_IndexerAndAuthorTagsMismatch_IndexerNotIncluded()
|
||||||
{
|
{
|
||||||
_mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition
|
_mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition
|
||||||
{
|
{
|
||||||
@@ -156,7 +157,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
|||||||
|
|
||||||
var allCriteria = WatchForSearchCriteria();
|
var allCriteria = WatchForSearchCriteria();
|
||||||
|
|
||||||
Subject.BookSearch(_firstBook, false, true, false);
|
await Subject.BookSearch(_firstBook, false, true, false);
|
||||||
|
|
||||||
var criteria = allCriteria.OfType<BookSearchCriteria>().ToList();
|
var criteria = allCriteria.OfType<BookSearchCriteria>().ToList();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@@ -26,15 +27,15 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_parse_recent_feed_from_FileList()
|
public async Task should_parse_recent_feed_from_FileList()
|
||||||
{
|
{
|
||||||
var recentFeed = ReadAllText(@"Files/Indexers/FileList/RecentFeed.json");
|
var recentFeed = ReadAllText(@"Files/Indexers/FileList/RecentFeed.json");
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>()
|
Mocker.GetMock<IHttpClient>()
|
||||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), recentFeed)));
|
||||||
|
|
||||||
var releases = Subject.FetchRecent();
|
var releases = await Subject.FetchRecent();
|
||||||
|
|
||||||
releases.Should().HaveCount(2);
|
releases.Should().HaveCount(2);
|
||||||
releases.First().Should().BeOfType<TorrentInfo>();
|
releases.First().Should().BeOfType<TorrentInfo>();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@@ -30,24 +31,24 @@ namespace NzbDrone.Core.Test.IndexerTests.GazelleTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_parse_recent_feed_from_gazelle()
|
public async Task should_parse_recent_feed_from_gazelle()
|
||||||
{
|
{
|
||||||
var recentFeed = ReadAllText(@"Files/Indexers/Gazelle/Gazelle.json");
|
var recentFeed = ReadAllText(@"Files/Indexers/Gazelle/Gazelle.json");
|
||||||
var indexFeed = ReadAllText(@"Files/Indexers/Gazelle/GazelleIndex.json");
|
var indexFeed = ReadAllText(@"Files/Indexers/Gazelle/GazelleIndex.json");
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>()
|
Mocker.GetMock<IHttpClient>()
|
||||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get && v.Url.FullUri.Contains("ajax.php?action=browse"))))
|
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get && v.Url.FullUri.Contains("ajax.php?action=browse"))))
|
||||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader { ContentType = "application/json" }, recentFeed));
|
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader { ContentType = "application/json" }, recentFeed)));
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>()
|
Mocker.GetMock<IHttpClient>()
|
||||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Post && v.Url.FullUri.Contains("ajax.php?action=index"))))
|
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Post && v.Url.FullUri.Contains("ajax.php?action=index"))))
|
||||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), indexFeed));
|
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), indexFeed)));
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>()
|
Mocker.GetMock<IHttpClient>()
|
||||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Post && v.Url.FullUri.Contains("login.php"))))
|
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Post && v.Url.FullUri.Contains("login.php"))))
|
||||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), indexFeed));
|
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), indexFeed)));
|
||||||
|
|
||||||
var releases = Subject.FetchRecent();
|
var releases = await Subject.FetchRecent();
|
||||||
|
|
||||||
releases.Should().HaveCount(4);
|
releases.Should().HaveCount(4);
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user