Compare commits

...

71 Commits

Author SHA1 Message Date
Bogdan e188c9aac0 Don't die when trying to open file with nullable path
Closes #3012
2023-10-19 17:35:23 +03:00
Bogdan a3ae2359f5 Fixed: Ignore case when cleansing announce URLs
(cherry picked from commit 41ed300899e8d7de82b1113d13ac6f6cf28cec17)
2023-10-19 17:06:40 +03:00
Bogdan 5b92905dd4 Bump version to 0.3.9 2023-10-15 07:51:31 +03:00
Weblate fc402743aa Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: DavidHenryThoreau <sorau@protonmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translation: Servarr/Readarr
2023-10-13 12:37:22 +03:00
Bogdan b9d53ed732 Add PostgreSQL specific query for cleaning multiple monitored editions
Fixes #2995
2023-10-12 02:20:28 +03:00
Bogdan d248747635 Fixed: Avoid logging evaluations when not using any Remote Path Mappings
(cherry picked from commit 44eb729ccc13237f4439006159bd616e8bdb5750)
2023-10-10 07:12:09 +03:00
Bogdan d70224c811 Add status test all button for IndexerLongTermStatusCheck
(cherry picked from commit 4ffa1816bd2305550abee20cea27e1296a99ddf6)
2023-10-10 07:11:58 +03:00
Bogdan acdf8c8aa8 Bump version to 0.3.8 2023-10-08 07:09:11 +03:00
Bogdan 3ed41554ce Log Notifiarr errors as warnings 2023-10-07 22:58:40 +03:00
Bogdan ce808c6d7b Prevent mapping null metadata responses
Fixes #2971
2023-10-07 01:40:49 +03:00
Weblate 63b1b56a4f Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Dimitri <dimitridroeck@gmail.com>
Co-authored-by: Garkus98 <ivan12061998@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: RicardoVelaC <ricardovelac@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: blankhang <blankhang@gmail.com>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-10-07 01:04:37 +03:00
Bogdan a5647bedc8 Remove reddit from support issues 2023-10-07 00:25:19 +03:00
Servarr fe659bb79d Automated API Docs update 2023-10-04 06:59:02 +03:00
MxMarx 9918535509 New: Author Added notification
(cherry picked from commit https://github.com/Radarr/Radarr/commit/f890aadffa5ae579bcf65abdcf3e3948837084a9)
2023-10-04 06:47:23 +03:00
William Brockhus f9a6db40b8 Fixed: Ignore timezone when comparing tag dates 2023-10-04 06:26:08 +03:00
Bogdan 6273d69ed6 Fix tests 2023-10-04 05:50:57 +03:00
dependabot[bot] 7012380e95 Bump postcss from 8.4.23 to 8.4.31
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.23 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.23...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-04 04:05:39 +03:00
Bogdan b001ecd698 Preserve the protocol for fanart images
Closes #2944
2023-10-01 17:29:46 +03:00
Bogdan e28becdda4 Preserve the protocol in Author Image
Closes #2942
2023-10-01 17:27:38 +03:00
Mark McDowall eae06695e8 Fixed: Completed downloads in Qbit missing import path
(cherry picked from commit 35365665cfd436ac276dd9591e23333bd26cf789)

Closes #2959
2023-10-01 17:23:21 +03:00
Weblate 54a9af2ced Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Stevie Robinson <stevie.robinson@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: mr cmuc <github@nextcos.de>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-10-01 17:20:42 +03:00
Stevie Robinson c9b55266fc Fixed: qBittorent history retention to allow at least 14 days seeding
(cherry picked from commit 33b87acabf2b4c71ee24cda1a466dec6f4f76996)
2023-10-01 17:20:16 +03:00
bakerboy448 05b64406a4 Fixed: Only apply remote path mappings for completed items in Qbit
(cherry picked from commit 583eb52ddc01b608ab6cb17e863a8830c17b7b75)
2023-10-01 17:20:06 +03:00
Bogdan 1f37c5387b Revert "Avoid returning null in static resource mapper Task"
This reverts commit d7305b9753.
2023-10-01 03:26:00 +03:00
Stevie Robinson 4a6c7042fe Fixed: SABnzbd history retention to allow at least 14 days
(cherry picked from commit a3938d8e0264b48b35f4715cbc15329fb489218a)
2023-09-27 19:47:13 +03:00
Bogdan d7305b9753 Avoid returning null in static resource mapper Task
(cherry picked from commit a1ea7accb32bc72f61ed4531d109f76fad843939)
2023-09-27 18:54:41 +03:00
Bogdan bd56643eaa Bump version to 0.3.7 2023-09-24 16:24:12 +03:00
Stevie Robinson 44e6de2e23 Add health check for dl clients removing completed downloads + enable for sab and qbit
(cherry picked from commit 7f2cd8a0e99b537a1c616998514bacdd8468a016)

Closes #2939
2023-09-19 21:44:45 +03:00
Mark McDowall b209d047fa Fixed: Don't try to create metadata images if source files doesn't exist
(cherry picked from commit 9a1022386a031c928fc0495d6ab990ebce605ec1)

Closes #2933
2023-09-19 21:37:09 +03:00
Mark McDowall fd5ab27df6 New: Don't treat 400 responses from Notifiarr as errors
(cherry picked from commit 5eb420bbe12f59d0a5392abf3d351be28ca210e6)

Closes #2938
2023-09-19 21:35:26 +03:00
Bogdan 4a89befd79 Log request failures in Notifiarr 2023-09-19 21:34:12 +03:00
Bogdan 1a30293c33 Check for empty description as well in ParseQuality 2023-09-19 21:33:26 +03:00
Bogdan f5c2a6bf51 Fix use of empty Author SortName in filename 2023-09-19 18:55:24 +03:00
Weblate f3d90fdaf1 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Akashi2020 <dieux02400@gmail.com>
Co-authored-by: Anthony Veaudry <anthonyveaudry@gmail.com>
Co-authored-by: Gyuyeop Kim <rlarbduq777@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Herve Lauwerier <hervelauwerier@gmail.com>
Co-authored-by: Richard de Souza Leite <rs9010482@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: mati300m <mateusz.smolec@gmail.com>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-09-19 14:32:26 +03:00
MxMarx 04c5671a0a Fixed: Release Push api broken when no indexer id is specified 2023-09-19 14:31:44 +03:00
Qstick 22cc88c5e7 Fixed: Show correct error on unauthorized caps call
(cherry picked from commit f2b0fc946e1fb1b4649f1b46a003bd2add09a461)
2023-09-19 14:28:41 +03:00
Bogdan ca0c95a2d2 Fixed: Skip parsing releases without title
(cherry picked from commit c7824bb593291634bf14a5f7aa689666969b03bf)
2023-09-19 14:28:14 +03:00
Mark McDowall 419f790d66 Fixed: Don't allow quality profile to be created without all qualities
(cherry picked from commit 32e1ae2f64827272d351991838200884876e52b4)
2023-09-19 14:28:01 +03:00
Bogdan 9fe08429bc Use await on reading the response content
(cherry picked from commit 82d586e7015d7ea06356ca436024a8af5a4fb677)
2023-09-18 03:24:30 +03:00
Bogdan 71f4a88ab3 Bump version to 0.3.6 2023-09-17 12:02:41 +03:00
Bogdan 30b283eda3 Fixed: Ignore inaccessible mount points
(cherry picked from commit 60f18249b05daa20523542beef54bc126d963d1e)
2023-09-14 06:40:05 +03:00
Bogdan e23d0bbfa1 Add housekeeping task to unmonitor multiple monitored editions 2023-09-10 20:30:55 +03:00
Weblate 765a2aa01b Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Qstick <qstick@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/da/
Translation: Servarr/Readarr
2023-09-10 20:30:40 +03:00
Bogdan 64895c3210 Bump version to 0.3.5 2023-09-10 09:04:03 +03:00
Weblate 03ab84a814 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: DavidJares <david.jares@me.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: He Zhu <zhuhe202@qq.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-09-10 00:33:32 -05:00
Bogdan b77e5b14e1 Fixed: macOS version detection
(cherry picked from commit 060be6177a5477c94823e6a423c42064dedc1afb)

Closes #2908
2023-09-08 05:08:09 +03:00
Bogdan 75efbd45e1 Fixed: Calculating seed time for qBittorrent
(cherry picked from commit 1b3ff64cc521396f9f1623617052c497649325a8)
2023-09-08 04:17:10 +03:00
Weblate 00cac507ad Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: DavidJares <david.jares@me.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-09-07 11:58:51 +03:00
Bogdan c4850505b0 New: Add Plex Media Server notifications 2023-09-07 10:18:24 +03:00
Bogdan 75213c86a1 Bump dotnet to 6.0.21 2023-09-05 16:30:21 +03:00
Bogdan b8c3a42643 Migrate to merged proposals now included in babel/present-env
Closes #2899
2023-09-05 03:03:33 +03:00
Bogdan 8acb034aa6 Use not allowed cursor for disabled select options
(cherry picked from commit 229a4bba05d1f42089aa92b1d938747e152590b2)
2023-09-05 02:58:09 +03:00
Bogdan 889d32552b Update UI dev packages 2023-09-01 14:29:20 +03:00
Bogdan adc5f4db97 Fixed: Increase timeout when downloading updates
(cherry picked from commit 467ce70291c793042ffb3ed8942c42e7bc1424d5)
2023-09-01 04:05:16 +03:00
Weblate 9d08050f96 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: He Zhu <zhuhe202@qq.com>
Co-authored-by: monopolo11 <bernardorn21@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-08-31 20:59:43 +03:00
Weblate f8cffbb4cf Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: AlexR-sf <omg.portal.supp@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: DavidJares <david.jares@me.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: He Zhu <zhuhe202@qq.com>
Co-authored-by: Renan da Mota Ciciliato <renanciciliato@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: brokje1988 <brokje1988@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-08-29 18:12:42 +03:00
Weblate 14aeb66142 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: He Zhu <zhuhe202@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-08-27 19:22:06 +03:00
Bogdan 37e8e11e31 Ensure the correct icons are spinning when refreshing authors and books 2023-08-25 21:51:52 +03:00
Bogdan bdb2f14936 Prevent NullRef in GetChangedAuthors when metadata is down 2023-08-25 21:49:15 +03:00
Mark McDowall a97af657be Improved UI error messages (stack trace and version)
(cherry picked from commit 37c355da51b654cea7309678c32a83a5cbe43d1f)

Closes #2207
2023-08-24 21:15:47 +03:00
Servarr 301127e6dc Automated API Docs update 2023-08-24 00:44:35 +03:00
Bogdan 1f95bcae4e New: Async HttpClient
(cherry picked from commit 0feee191462dd3e5dde66e476e8b4b46a85ec4f0)
2023-08-24 00:38:31 +03:00
Bogdan 29118cda45 New: Use HTTP/2 in HttpClient
(cherry picked from commit 78593f428acc578785f9ecfdd41fbf2443d93d84)
2023-08-24 00:38:31 +03:00
Bogdan 09beaa939d Fixed: (FileList) Prevent double query escaping in search requests 2023-08-24 00:38:31 +03:00
Bogdan 2107624f1c Prevent health checks warnings for disabled notifications
(cherry picked from commit 5a7f42a63e25d6abdb187c37e92a908a6b85fb4d)
2023-08-23 04:58:22 +03:00
Weblate c1c2076e5c Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translation: Servarr/Readarr
2023-08-23 04:57:55 +03:00
Bogdan c31a797bd8 Revert "Switch to Parallel.ForEach for list processing with MaxParallelism"
This reverts commit ebb2b4eca3.
2023-08-22 06:03:14 +03:00
Qstick ebb2b4eca3 Switch to Parallel.ForEach for list processing with MaxParallelism
(cherry picked from commit 0f93e04186f24abdb0cf0b3ba6a3505fda834e06)
2023-08-21 04:44:59 +03:00
Qstick 3ec5d9b9fe Use default MemoryAllocator for ImageSharp resizing
(cherry picked from commit c1a3a8249befde0a1b68e7845d5d2346066457a1)
2023-08-21 04:42:59 +03:00
Qstick 1ad84a7c44 Fixed: Ignore case when comparing torrent infohash
(cherry picked from commit 7986488c6d1687b0810b3bcac2c1dae725e770ac)
2023-08-20 16:10:26 -05:00
Bogdan 9d67c18254 Bump version to 0.3.4 2023-08-20 12:24:38 +03:00
185 changed files with 4094 additions and 1860 deletions
+1 -1
View File
@@ -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
View File
@@ -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.
+1 -2
View File
@@ -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
View File
@@ -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.9'
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.413'
nodeVersion: '16.X' nodeVersion: '16.X'
innoVersion: '6.2.0' innoVersion: '6.2.0'
windowsImage: 'windows-2022' windowsImage: 'windows-2022'
+4 -4
View File
@@ -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: {
+3 -6
View File
@@ -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;
} }
} }
@@ -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 {
@@ -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
}; };
} }
@@ -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),
@@ -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 {
@@ -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;
@@ -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}
+18 -20
View File
@@ -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.21",
"@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"
+6 -6
View File
@@ -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.1" />
<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,7 +16,7 @@
<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.21" />
<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" />
@@ -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.8" />
<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
}; };
+71 -21
View File
@@ -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)
+1
View File
@@ -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;
+8 -5
View File
@@ -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),
+31 -7
View File
@@ -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);
}
} }
} }
} }
@@ -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();
}
} }
} }
@@ -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();
@@ -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();
} }
} }
} }
@@ -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>()))
@@ -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());
@@ -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());
@@ -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)));
} }
@@ -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());
} }
} }
} }
@@ -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;
@@ -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")]
@@ -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);
} }
@@ -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());
@@ -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);
}
}
}
@@ -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);
@@ -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;
@@ -84,15 +85,15 @@ namespace NzbDrone.Core.Test.IndexerTests.IPTorrentsTests
} }
[Test] [Test]
public void should_parse_recent_feed_from_IPTorrents() public async Task should_parse_recent_feed_from_IPTorrents()
{ {
var recentFeed = ReadAllText(@"Files/Indexers/IPTorrents/IPTorrents.xml"); var recentFeed = ReadAllText(@"Files/Indexers/IPTorrents/IPTorrents.xml");
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(5); releases.Should().HaveCount(5);
releases.First().Should().BeOfType<TorrentInfo>(); releases.First().Should().BeOfType<TorrentInfo>();
@@ -2,6 +2,7 @@ using System;
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 FluentAssertions; using FluentAssertions;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
@@ -39,15 +40,15 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
} }
[Test] [Test]
public void should_parse_recent_feed_from_newznab_nzb_su() public async Task should_parse_recent_feed_from_newznab_nzb_su()
{ {
var recentFeed = ReadAllText(@"Files/Indexers/Newznab/newznab_nzb_su.xml"); var recentFeed = ReadAllText(@"Files/Indexers/Newznab/newznab_nzb_su.xml");
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(100); releases.Should().HaveCount(100);
@@ -83,7 +84,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
} }
[Test] [Test]
public void should_record_indexer_failure_if_caps_throw() public async Task should_record_indexer_failure_if_caps_throw()
{ {
var request = new HttpRequest("http://my.indexer.com"); var request = new HttpRequest("http://my.indexer.com");
var response = new HttpResponse(request, new HttpHeader(), new byte[0], (HttpStatusCode)429); var response = new HttpResponse(request, new HttpHeader(), new byte[0], (HttpStatusCode)429);
@@ -96,7 +97,9 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
_caps.MaxPageSize = 30; _caps.MaxPageSize = 30;
_caps.DefaultPageSize = 25; _caps.DefaultPageSize = 25;
Subject.FetchRecent().Should().BeEmpty(); var releases = await Subject.FetchRecent();
releases.Should().BeEmpty();
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());
@@ -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.NyaaTests
} }
[Test] [Test]
public void should_parse_recent_feed_from_Nyaa() public async Task should_parse_recent_feed_from_Nyaa()
{ {
var recentFeed = ReadAllText(@"Files/Indexers/Nyaa/Nyaa.xml"); var recentFeed = ReadAllText(@"Files/Indexers/Nyaa/Nyaa.xml");
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(4); releases.Should().HaveCount(4);
releases.First().Should().BeOfType<TorrentInfo>(); releases.First().Should().BeOfType<TorrentInfo>();
@@ -22,7 +22,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
{ {
var result = new List<ValidationFailure>(); var result = new List<ValidationFailure>();
SetupNLog(); // Enable this to enable trace logging with nlog for debugging purposes SetupNLog(); // Enable this to enable trace logging with nlog for debugging purposes
Test(result); Test(result).GetAwaiter().GetResult();
return result; return result;
} }
@@ -1,5 +1,6 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using FluentAssertions; using FluentAssertions;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
@@ -34,17 +35,21 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
{ {
var recentFeed = ReadAllText(@"Files/Indexers/" + rssXmlFile); var recentFeed = ReadAllText(@"Files/Indexers/" + rssXmlFile);
Mocker.GetMock<IHttpClient>()
.Setup(o => o.ExecuteAsync(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), recentFeed)));
Mocker.GetMock<IHttpClient>() Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.IsAny<HttpRequest>())) .Setup(o => o.Execute(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed)); .Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
} }
[Test] [Test]
public void should_parse_recent_feed_from_ImmortalSeed() public async Task should_parse_recent_feed_from_ImmortalSeed()
{ {
GivenRecentFeedResponse("TorrentRss/ImmortalSeed.xml"); GivenRecentFeedResponse("TorrentRss/ImmortalSeed.xml");
var releases = Subject.FetchRecent(); var releases = await Subject.FetchRecent();
releases.Should().HaveCount(50); releases.Should().HaveCount(50);
releases.First().Should().BeOfType<TorrentInfo>(); releases.First().Should().BeOfType<TorrentInfo>();
@@ -66,11 +71,11 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
} }
[Test] [Test]
public void should_parse_recent_feed_from_Ezrss() public async Task should_parse_recent_feed_from_Ezrss()
{ {
GivenRecentFeedResponse("TorrentRss/Ezrss.xml"); GivenRecentFeedResponse("TorrentRss/Ezrss.xml");
var releases = Subject.FetchRecent(); var releases = await Subject.FetchRecent();
releases.Should().HaveCount(3); releases.Should().HaveCount(3);
releases.First().Should().BeOfType<TorrentInfo>(); releases.First().Should().BeOfType<TorrentInfo>();
@@ -92,13 +97,13 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
} }
[Test] [Test]
public void should_parse_recent_feed_from_ShowRSS_info() public async Task should_parse_recent_feed_from_ShowRSS_info()
{ {
Subject.Definition.Settings.As<TorrentRssIndexerSettings>().AllowZeroSize = true; Subject.Definition.Settings.As<TorrentRssIndexerSettings>().AllowZeroSize = true;
GivenRecentFeedResponse("TorrentRss/ShowRSS.info.xml"); GivenRecentFeedResponse("TorrentRss/ShowRSS.info.xml");
var releases = Subject.FetchRecent(); var releases = await Subject.FetchRecent();
releases.Should().HaveCount(5); releases.Should().HaveCount(5);
releases.First().Should().BeOfType<TorrentInfo>(); releases.First().Should().BeOfType<TorrentInfo>();
@@ -120,13 +125,13 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
} }
[Test] [Test]
public void should_parse_recent_feed_from_Doki() public async Task should_parse_recent_feed_from_Doki()
{ {
Subject.Definition.Settings.As<TorrentRssIndexerSettings>().AllowZeroSize = true; Subject.Definition.Settings.As<TorrentRssIndexerSettings>().AllowZeroSize = true;
GivenRecentFeedResponse("TorrentRss/Doki.xml"); GivenRecentFeedResponse("TorrentRss/Doki.xml");
var releases = Subject.FetchRecent(); var releases = await Subject.FetchRecent();
releases.Should().HaveCount(5); releases.Should().HaveCount(5);
releases.First().Should().BeOfType<TorrentInfo>(); releases.First().Should().BeOfType<TorrentInfo>();
@@ -148,11 +153,11 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
} }
[Test] [Test]
public void should_parse_recent_feed_from_ExtraTorrents() public async Task should_parse_recent_feed_from_ExtraTorrents()
{ {
GivenRecentFeedResponse("TorrentRss/ExtraTorrents.xml"); GivenRecentFeedResponse("TorrentRss/ExtraTorrents.xml");
var releases = Subject.FetchRecent(); var releases = await Subject.FetchRecent();
releases.Should().HaveCount(5); releases.Should().HaveCount(5);
releases.First().Should().BeOfType<TorrentInfo>(); releases.First().Should().BeOfType<TorrentInfo>();
@@ -174,11 +179,11 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
} }
[Test] [Test]
public void should_parse_recent_feed_from_LimeTorrents() public async Task should_parse_recent_feed_from_LimeTorrents()
{ {
GivenRecentFeedResponse("TorrentRss/LimeTorrents.xml"); GivenRecentFeedResponse("TorrentRss/LimeTorrents.xml");
var releases = Subject.FetchRecent(); var releases = await Subject.FetchRecent();
releases.Should().HaveCount(5); releases.Should().HaveCount(5);
releases.First().Should().BeOfType<TorrentInfo>(); releases.First().Should().BeOfType<TorrentInfo>();
@@ -200,11 +205,11 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
} }
[Test] [Test]
public void should_parse_recent_feed_from_AnimeTosho_without_size() public async Task should_parse_recent_feed_from_AnimeTosho_without_size()
{ {
GivenRecentFeedResponse("TorrentRss/AnimeTosho_NoSize.xml"); GivenRecentFeedResponse("TorrentRss/AnimeTosho_NoSize.xml");
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>();
@@ -226,11 +231,11 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
} }
[Test] [Test]
public void should_parse_multi_enclosure_from_AnimeTosho() public async Task should_parse_multi_enclosure_from_AnimeTosho()
{ {
GivenRecentFeedResponse("TorrentRss/AnimeTosho_NoSize.xml"); GivenRecentFeedResponse("TorrentRss/AnimeTosho_NoSize.xml");
var releases = Subject.FetchRecent(); var releases = await Subject.FetchRecent();
releases.Should().HaveCount(2); releases.Should().HaveCount(2);
releases.Last().Should().BeOfType<TorrentInfo>(); releases.Last().Should().BeOfType<TorrentInfo>();
@@ -243,11 +248,11 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
} }
[Test] [Test]
public void should_parse_recent_feed_from_AlphaRatio() public async Task should_parse_recent_feed_from_AlphaRatio()
{ {
GivenRecentFeedResponse("TorrentRss/AlphaRatio.xml"); GivenRecentFeedResponse("TorrentRss/AlphaRatio.xml");
var releases = Subject.FetchRecent(); var releases = await Subject.FetchRecent();
releases.Should().HaveCount(2); releases.Should().HaveCount(2);
releases.Last().Should().BeOfType<TorrentInfo>(); releases.Last().Should().BeOfType<TorrentInfo>();
@@ -260,12 +265,12 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
} }
[Test] [Test]
public void should_parse_recent_feed_from_EveolutionWorld_without_size() public async Task should_parse_recent_feed_from_EveolutionWorld_without_size()
{ {
Subject.Definition.Settings.As<TorrentRssIndexerSettings>().AllowZeroSize = true; Subject.Definition.Settings.As<TorrentRssIndexerSettings>().AllowZeroSize = true;
GivenRecentFeedResponse("TorrentRss/EvolutionWorld.xml"); GivenRecentFeedResponse("TorrentRss/EvolutionWorld.xml");
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>();
@@ -287,11 +292,13 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
} }
[Test] [Test]
public void should_record_indexer_failure_if_unsupported_feed() public async Task should_record_indexer_failure_if_unsupported_feed()
{ {
GivenRecentFeedResponse("TorrentRss/invalid/TorrentDay_NoPubDate.xml"); GivenRecentFeedResponse("TorrentRss/invalid/TorrentDay_NoPubDate.xml");
Subject.FetchRecent().Should().BeEmpty(); var releases = await Subject.FetchRecent();
releases.Should().BeEmpty();
Mocker.GetMock<IIndexerStatusService>() Mocker.GetMock<IIndexerStatusService>()
.Verify(v => v.RecordFailure(It.IsAny<int>(), TimeSpan.Zero), Times.Once()); .Verify(v => v.RecordFailure(It.IsAny<int>(), TimeSpan.Zero), Times.Once());
@@ -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.TorrentleechTests
} }
[Test] [Test]
public void should_parse_recent_feed_from_Torrentleech() public async Task should_parse_recent_feed_from_Torrentleech()
{ {
var recentFeed = ReadAllText(@"Files/Indexers/Torrentleech/Torrentleech.xml"); var recentFeed = ReadAllText(@"Files/Indexers/Torrentleech/Torrentleech.xml");
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(5); releases.Should().HaveCount(5);
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 FizzWare.NBuilder; using FizzWare.NBuilder;
using FluentAssertions; using FluentAssertions;
using Moq; using Moq;
@@ -44,15 +45,15 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
} }
[Test] [Test]
public void should_parse_recent_feed_from_torznab_hdaccess_net() public async Task should_parse_recent_feed_from_torznab_hdaccess_net()
{ {
var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_hdaccess_net.xml"); var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_hdaccess_net.xml");
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(5); releases.Should().HaveCount(5);
@@ -73,15 +74,15 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
} }
[Test] [Test]
public void should_parse_recent_feed_from_torznab_tpb() public async Task should_parse_recent_feed_from_torznab_tpb()
{ {
var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_tpb.xml"); var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_tpb.xml");
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(5); releases.Should().HaveCount(5);
@@ -140,8 +141,8 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
(Subject.Definition.Settings as TorznabSettings).BaseUrl = baseUrl; (Subject.Definition.Settings as TorznabSettings).BaseUrl = baseUrl;
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 result = new NzbDroneValidationResult(Subject.Test()); var result = new NzbDroneValidationResult(Subject.Test());
result.IsValid.Should().BeTrue(); result.IsValid.Should().BeTrue();
@@ -155,8 +156,8 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_tpb.xml"); var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_tpb.xml");
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)));
(Subject.Definition.Settings as TorznabSettings).ApiPath = apiPath; (Subject.Definition.Settings as TorznabSettings).ApiPath = apiPath;
@@ -64,6 +64,11 @@ namespace NzbDrone.Core.Test.NotificationTests
TestLogger.Info("OnRename was called"); TestLogger.Info("OnRename was called");
} }
public override void OnAuthorAdded(Author author)
{
TestLogger.Info("OnAuthorAdded was called");
}
public override void OnAuthorDelete(AuthorDeleteMessage message) public override void OnAuthorDelete(AuthorDeleteMessage message)
{ {
TestLogger.Info("OnAuthorDelete was called"); TestLogger.Info("OnAuthorDelete was called");
@@ -138,6 +143,7 @@ namespace NzbDrone.Core.Test.NotificationTests
notification.SupportsOnUpgrade.Should().BeTrue(); notification.SupportsOnUpgrade.Should().BeTrue();
notification.SupportsOnRename.Should().BeTrue(); notification.SupportsOnRename.Should().BeTrue();
notification.SupportsOnHealthIssue.Should().BeTrue(); notification.SupportsOnHealthIssue.Should().BeTrue();
notification.SupportsOnAuthorAdded.Should().BeTrue();
notification.SupportsOnAuthorDelete.Should().BeTrue(); notification.SupportsOnAuthorDelete.Should().BeTrue();
notification.SupportsOnBookDelete.Should().BeTrue(); notification.SupportsOnBookDelete.Should().BeTrue();
notification.SupportsOnBookFileDelete.Should().BeTrue(); notification.SupportsOnBookFileDelete.Should().BeTrue();
@@ -157,6 +163,7 @@ namespace NzbDrone.Core.Test.NotificationTests
notification.SupportsOnReleaseImport.Should().BeFalse(); notification.SupportsOnReleaseImport.Should().BeFalse();
notification.SupportsOnUpgrade.Should().BeFalse(); notification.SupportsOnUpgrade.Should().BeFalse();
notification.SupportsOnRename.Should().BeFalse(); notification.SupportsOnRename.Should().BeFalse();
notification.SupportsOnAuthorAdded.Should().BeFalse();
notification.SupportsOnAuthorDelete.Should().BeFalse(); notification.SupportsOnAuthorDelete.Should().BeFalse();
notification.SupportsOnBookDelete.Should().BeFalse(); notification.SupportsOnBookDelete.Should().BeFalse();
notification.SupportsOnBookFileDelete.Should().BeFalse(); notification.SupportsOnBookFileDelete.Should().BeFalse();
@@ -122,7 +122,7 @@ namespace NzbDrone.Core.Blocklisting
{ {
if (release.InfoHash.IsNotNullOrWhiteSpace()) if (release.InfoHash.IsNotNullOrWhiteSpace())
{ {
return release.InfoHash.Equals(item.TorrentInfoHash); return release.InfoHash.Equals(item.TorrentInfoHash, StringComparison.InvariantCultureIgnoreCase);
} }
return HasSameIndexer(item, release.Indexer); return HasSameIndexer(item, release.Indexer);
@@ -275,7 +275,7 @@ namespace NzbDrone.Core.Books.Calibre
var updatedPath = GetOriginalFormat(updated.Formats); var updatedPath = GetOriginalFormat(updated.Formats);
if (updatedPath != file.Path) if (updatedPath != null && updatedPath != file.Path)
{ {
_rootFolderWatchingService.ReportFileSystemChangeBeginning(updatedPath); _rootFolderWatchingService.ReportFileSystemChangeBeginning(updatedPath);
file.Path = updatedPath; file.Path = updatedPath;
@@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(038)]
public class add_on_author_added_to_notifications : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Notifications").AddColumn("OnAuthorAdded").AsBoolean().WithDefaultValue(false);
}
}
}
@@ -85,6 +85,7 @@ namespace NzbDrone.Core.Datastore
.Ignore(i => i.SupportsOnReleaseImport) .Ignore(i => i.SupportsOnReleaseImport)
.Ignore(i => i.SupportsOnUpgrade) .Ignore(i => i.SupportsOnUpgrade)
.Ignore(i => i.SupportsOnRename) .Ignore(i => i.SupportsOnRename)
.Ignore(i => i.SupportsOnAuthorAdded)
.Ignore(i => i.SupportsOnAuthorDelete) .Ignore(i => i.SupportsOnAuthorDelete)
.Ignore(i => i.SupportsOnBookDelete) .Ignore(i => i.SupportsOnBookDelete)
.Ignore(i => i.SupportsOnBookFileDelete) .Ignore(i => i.SupportsOnBookFileDelete)
@@ -1,6 +1,7 @@
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@@ -10,12 +11,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
public class IndexerTagSpecification : IDecisionEngineSpecification public class IndexerTagSpecification : IDecisionEngineSpecification
{ {
private readonly Logger _logger; private readonly Logger _logger;
private readonly IIndexerRepository _indexerRepository; private readonly IIndexerFactory _indexerFactory;
public IndexerTagSpecification(Logger logger, IIndexerRepository indexerRepository) public IndexerTagSpecification(Logger logger, IIndexerFactory indexerFactory)
{ {
_logger = logger; _logger = logger;
_indexerRepository = indexerRepository; _indexerFactory = indexerFactory;
} }
public SpecificationPriority Priority => SpecificationPriority.Default; public SpecificationPriority Priority => SpecificationPriority.Default;
@@ -23,8 +24,24 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
public virtual Decision IsSatisfiedBy(RemoteBook subject, SearchCriteriaBase searchCriteria) public virtual Decision IsSatisfiedBy(RemoteBook subject, SearchCriteriaBase searchCriteria)
{ {
// If indexer has tags, check that at least one of them is present on the author if (subject.Release == null || subject.Author?.Tags == null || subject.Release.IndexerId == 0)
var indexerTags = _indexerRepository.Get(subject.Release.IndexerId).Tags; {
return Decision.Accept();
}
IndexerDefinition indexer;
try
{
indexer = _indexerFactory.Get(subject.Release.IndexerId);
}
catch (ModelNotFoundException)
{
_logger.Debug("Indexer with id {0} does not exist, skipping indexer tags check", subject.Release.IndexerId);
return Decision.Accept();
}
// If indexer has tags, check that at least one of them is present on the series
var indexerTags = indexer.Tags;
if (indexerTags.Any() && indexerTags.Intersect(subject.Author.Tags).Empty()) if (indexerTags.Any() && indexerTags.Intersect(subject.Author.Tags).Empty())
{ {
@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks;
using FluentValidation.Results; using FluentValidation.Results;
using NLog; using NLog;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
@@ -32,7 +33,7 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
public override DownloadProtocol Protocol => DownloadProtocol.Usenet; public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
public override string Download(RemoteBook remoteBook, IIndexer indexer) public override async Task<string> Download(RemoteBook remoteBook, IIndexer indexer)
{ {
var url = remoteBook.Release.DownloadUrl; var url = remoteBook.Release.DownloadUrl;
var title = remoteBook.Release.Title; var title = remoteBook.Release.Title;
@@ -48,7 +49,7 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
var nzbFile = Path.Combine(Settings.NzbFolder, title + ".nzb"); var nzbFile = Path.Combine(Settings.NzbFolder, title + ".nzb");
_logger.Debug("Downloading NZB from: {0} to: {1}", url, nzbFile); _logger.Debug("Downloading NZB from: {0} to: {1}", url, nzbFile);
_httpClient.DownloadFile(url, nzbFile); await _httpClient.DownloadFileAsync(url, nzbFile);
_logger.Debug("NZB Download succeeded, saved to: {0}", nzbFile); _logger.Debug("NZB Download succeeded, saved to: {0}", nzbFile);
@@ -304,13 +304,13 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
break; break;
} }
if (version >= new Version("2.6.1")) if (version >= new Version("2.6.1") && item.Status == DownloadItemStatus.Completed)
{ {
if (torrent.ContentPath != torrent.SavePath) if (torrent.ContentPath != torrent.SavePath)
{ {
item.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(torrent.ContentPath)); item.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(torrent.ContentPath));
} }
else if (item.Status == DownloadItemStatus.Completed) else
{ {
item.Status = DownloadItemStatus.Warning; item.Status = DownloadItemStatus.Warning;
item.Message = "Unable to Import. Path matches client base download directory, it's possible 'Keep top-level folder' is disabled for this torrent or 'Torrent Content Layout' is NOT set to 'Original' or 'Create Subfolder'?"; item.Message = "Unable to Import. Path matches client base download directory, it's possible 'Keep top-level folder' is disabled for this torrent or 'Torrent Content Layout' is NOT set to 'Original' or 'Create Subfolder'?";
@@ -386,10 +386,13 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
} }
} }
var minimumRetention = 60 * 24 * 14;
return new DownloadClientInfo return new DownloadClientInfo
{ {
IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost", IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost",
OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, destDir) } OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, destDir) },
RemovesCompletedDownloads = (config.MaxRatioEnabled || (config.MaxSeedingTimeEnabled && config.MaxSeedingTime < minimumRetention)) && (config.MaxRatioAction == QBittorrentMaxRatioAction.Remove || config.MaxRatioAction == QBittorrentMaxRatioAction.DeleteFiles)
}; };
} }
@@ -628,11 +631,11 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
if (torrent.SeedingTimeLimit >= 0) if (torrent.SeedingTimeLimit >= 0)
{ {
seedingTimeLimit = torrent.SeedingTimeLimit; seedingTimeLimit = torrent.SeedingTimeLimit * 60;
} }
else if (torrent.SeedingTimeLimit == -2 && config.MaxSeedingTimeEnabled) else if (torrent.SeedingTimeLimit == -2 && config.MaxSeedingTimeEnabled)
{ {
seedingTimeLimit = config.MaxSeedingTime; seedingTimeLimit = config.MaxSeedingTime * 60;
} }
else else
{ {
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
public float RatioLimit { get; set; } = -2; public float RatioLimit { get; set; } = -2;
[JsonProperty(PropertyName = "seeding_time")] [JsonProperty(PropertyName = "seeding_time")]
public long? SeedingTime { get; set; } // Torrent seeding time (not provided by the list api) public long? SeedingTime { get; set; } // Torrent seeding time (in seconds, not provided by the list api)
[JsonProperty(PropertyName = "seeding_time_limit")] // Per torrent seeding time limit (-2 = use global, -1 = unlimited) [JsonProperty(PropertyName = "seeding_time_limit")] // Per torrent seeding time limit (-2 = use global, -1 = unlimited)
public long SeedingTimeLimit { get; set; } = -2; public long SeedingTimeLimit { get; set; } = -2;
@@ -47,7 +47,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
public string SavePath { get; set; } public string SavePath { get; set; }
[JsonProperty(PropertyName = "seeding_time")] [JsonProperty(PropertyName = "seeding_time")]
public long SeedingTime { get; set; } // Torrent seeding time public long SeedingTime { get; set; } // Torrent seeding time (in seconds)
} }
public class QBittorrentTorrentFile public class QBittorrentTorrentFile
@@ -263,6 +263,17 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
status.OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, category.FullPath) }; status.OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, category.FullPath) };
} }
if (config.Misc.history_retention.IsNotNullOrWhiteSpace() && config.Misc.history_retention.EndsWith("d"))
{
int.TryParse(config.Misc.history_retention.AsSpan(0, config.Misc.history_retention.Length - 1),
out var daysRetention);
status.RemovesCompletedDownloads = daysRetention < 14;
}
else
{
status.RemovesCompletedDownloads = config.Misc.history_retention != "0";
}
return status; return status;
} }
@@ -29,6 +29,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
public string[] date_categories { get; set; } public string[] date_categories { get; set; }
public bool enable_date_sorting { get; set; } public bool enable_date_sorting { get; set; }
public bool pre_check { get; set; } public bool pre_check { get; set; }
public string history_retention { get; set; }
} }
public class SabnzbdCategory public class SabnzbdCategory
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using FluentValidation.Results; using FluentValidation.Results;
using NLog; using NLog;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
@@ -58,7 +59,7 @@ namespace NzbDrone.Core.Download
get; get;
} }
public abstract string Download(RemoteBook remoteBook, IIndexer indexer); public abstract Task<string> Download(RemoteBook remoteBook, IIndexer indexer);
public abstract IEnumerable<DownloadClientItem> GetItems(); public abstract IEnumerable<DownloadClientItem> GetItems();
public virtual DownloadClientItem GetImportItem(DownloadClientItem item, DownloadClientItem previousImportAttempt) public virtual DownloadClientItem GetImportItem(DownloadClientItem item, DownloadClientItem previousImportAttempt)
@@ -11,6 +11,7 @@ namespace NzbDrone.Core.Download
} }
public bool IsLocalhost { get; set; } public bool IsLocalhost { get; set; }
public bool RemovesCompletedDownloads { get; set; }
public List<OsPath> OutputRootFolders { get; set; } public List<OsPath> OutputRootFolders { get; set; }
} }
} }
+17 -9
View File
@@ -1,4 +1,5 @@
using System; using System;
using System.Threading.Tasks;
using NLog; using NLog;
using NzbDrone.Common.EnsureThat; using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
@@ -16,7 +17,7 @@ namespace NzbDrone.Core.Download
{ {
public interface IDownloadService public interface IDownloadService
{ {
void DownloadReport(RemoteBook remoteBook); Task DownloadReport(RemoteBook remoteBook);
} }
public class DownloadService : IDownloadService public class DownloadService : IDownloadService
@@ -49,15 +50,23 @@ namespace NzbDrone.Core.Download
_logger = logger; _logger = logger;
} }
public void DownloadReport(RemoteBook remoteBook) public async Task DownloadReport(RemoteBook remoteBook)
{
var filterBlockedClients = remoteBook.Release.PendingReleaseReason == PendingReleaseReason.DownloadClientUnavailable;
var tags = remoteBook.Author?.Tags;
var downloadClient = _downloadClientProvider.GetDownloadClient(remoteBook.Release.DownloadProtocol, remoteBook.Release.IndexerId, filterBlockedClients, tags);
await DownloadReport(remoteBook, downloadClient);
}
private async Task DownloadReport(RemoteBook remoteBook, IDownloadClient downloadClient)
{ {
Ensure.That(remoteBook.Author, () => remoteBook.Author).IsNotNull(); Ensure.That(remoteBook.Author, () => remoteBook.Author).IsNotNull();
Ensure.That(remoteBook.Books, () => remoteBook.Books).HasItems(); Ensure.That(remoteBook.Books, () => remoteBook.Books).HasItems();
var downloadTitle = remoteBook.Release.Title; var downloadTitle = remoteBook.Release.Title;
var filterBlockedClients = remoteBook.Release.PendingReleaseReason == PendingReleaseReason.DownloadClientUnavailable;
var tags = remoteBook.Author?.Tags;
var downloadClient = _downloadClientProvider.GetDownloadClient(remoteBook.Release.DownloadProtocol, remoteBook.Release.IndexerId, filterBlockedClients, tags);
if (downloadClient == null) if (downloadClient == null)
{ {
@@ -71,7 +80,7 @@ namespace NzbDrone.Core.Download
if (remoteBook.Release.DownloadUrl.IsNotNullOrWhiteSpace() && !remoteBook.Release.DownloadUrl.StartsWith("magnet:")) if (remoteBook.Release.DownloadUrl.IsNotNullOrWhiteSpace() && !remoteBook.Release.DownloadUrl.StartsWith("magnet:"))
{ {
var url = new HttpUri(remoteBook.Release.DownloadUrl); var url = new HttpUri(remoteBook.Release.DownloadUrl);
_rateLimitService.WaitAndPulse(url.Host, TimeSpan.FromSeconds(2)); await _rateLimitService.WaitAndPulseAsync(url.Host, TimeSpan.FromSeconds(2));
} }
IIndexer indexer = null; IIndexer indexer = null;
@@ -84,7 +93,7 @@ namespace NzbDrone.Core.Download
string downloadClientId; string downloadClientId;
try try
{ {
downloadClientId = downloadClient.Download(remoteBook, indexer); downloadClientId = await downloadClient.Download(remoteBook, indexer);
_downloadClientStatusService.RecordSuccess(downloadClient.Definition.Id); _downloadClientStatusService.RecordSuccess(downloadClient.Definition.Id);
_indexerStatusService.RecordSuccess(remoteBook.Release.IndexerId); _indexerStatusService.RecordSuccess(remoteBook.Release.IndexerId);
} }
@@ -100,8 +109,7 @@ namespace NzbDrone.Core.Download
} }
catch (ReleaseDownloadException ex) catch (ReleaseDownloadException ex)
{ {
var http429 = ex.InnerException as TooManyRequestsException; if (ex.InnerException is TooManyRequestsException http429)
if (http429 != null)
{ {
_indexerStatusService.RecordFailure(remoteBook.Release.IndexerId, http429.RetryAfter); _indexerStatusService.RecordFailure(remoteBook.Release.IndexerId, http429.RetryAfter);
} }
@@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
@@ -8,7 +9,7 @@ namespace NzbDrone.Core.Download
public interface IDownloadClient : IProvider public interface IDownloadClient : IProvider
{ {
DownloadProtocol Protocol { get; } DownloadProtocol Protocol { get; }
string Download(RemoteBook remoteBook, IIndexer indexer); Task<string> Download(RemoteBook remoteBook, IIndexer indexer);
IEnumerable<DownloadClientItem> GetItems(); IEnumerable<DownloadClientItem> GetItems();
DownloadClientItem GetImportItem(DownloadClientItem item, DownloadClientItem previousImportAttempt); DownloadClientItem GetImportItem(DownloadClientItem item, DownloadClientItem previousImportAttempt);
void RemoveItem(DownloadClientItem item, bool deleteData); void RemoveItem(DownloadClientItem item, bool deleteData);
@@ -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 NLog; using NLog;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download.Clients; using NzbDrone.Core.Download.Clients;
@@ -12,7 +13,7 @@ namespace NzbDrone.Core.Download
{ {
public interface IProcessDownloadDecisions public interface IProcessDownloadDecisions
{ {
ProcessedDecisions ProcessDecisions(List<DownloadDecision> decisions); Task<ProcessedDecisions> ProcessDecisions(List<DownloadDecision> decisions);
} }
public class ProcessDownloadDecisions : IProcessDownloadDecisions public class ProcessDownloadDecisions : IProcessDownloadDecisions
@@ -33,7 +34,7 @@ namespace NzbDrone.Core.Download
_logger = logger; _logger = logger;
} }
public ProcessedDecisions ProcessDecisions(List<DownloadDecision> decisions) public async Task<ProcessedDecisions> ProcessDecisions(List<DownloadDecision> decisions)
{ {
var qualifiedReports = GetQualifiedReports(decisions); var qualifiedReports = GetQualifiedReports(decisions);
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(qualifiedReports); var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(qualifiedReports);
@@ -75,7 +76,7 @@ namespace NzbDrone.Core.Download
try try
{ {
_logger.Trace("Grabbing from Indexer {0} at priority {1}.", remoteBook.Release.Indexer, remoteBook.Release.IndexerPriority); _logger.Trace("Grabbing from Indexer {0} at priority {1}.", remoteBook.Release.Indexer, remoteBook.Release.IndexerPriority);
_downloadService.DownloadReport(remoteBook); await _downloadService.DownloadReport(remoteBook);
grabbed.Add(report); grabbed.Add(report);
} }
catch (ReleaseUnavailableException) catch (ReleaseUnavailableException)
@@ -1,5 +1,6 @@
using System; using System;
using System.Net; using System.Net;
using System.Threading.Tasks;
using MonoTorrent; using MonoTorrent;
using NLog; using NLog;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
@@ -41,7 +42,7 @@ namespace NzbDrone.Core.Download
protected abstract string AddFromMagnetLink(RemoteBook remoteBook, string hash, string magnetLink); protected abstract string AddFromMagnetLink(RemoteBook remoteBook, string hash, string magnetLink);
protected abstract string AddFromTorrentFile(RemoteBook remoteBook, string hash, string filename, byte[] fileContent); protected abstract string AddFromTorrentFile(RemoteBook remoteBook, string hash, string filename, byte[] fileContent);
public override string Download(RemoteBook remoteBook, IIndexer indexer) public override async Task<string> Download(RemoteBook remoteBook, IIndexer indexer)
{ {
var torrentInfo = remoteBook.Release as TorrentInfo; var torrentInfo = remoteBook.Release as TorrentInfo;
@@ -68,7 +69,7 @@ namespace NzbDrone.Core.Download
{ {
try try
{ {
return DownloadFromWebUrl(remoteBook, indexer, torrentUrl); return await DownloadFromWebUrl(remoteBook, indexer, torrentUrl);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -114,14 +115,14 @@ namespace NzbDrone.Core.Download
if (torrentUrl.IsNotNullOrWhiteSpace()) if (torrentUrl.IsNotNullOrWhiteSpace())
{ {
return DownloadFromWebUrl(remoteBook, indexer, torrentUrl); return await DownloadFromWebUrl(remoteBook, indexer, torrentUrl);
} }
} }
return null; return null;
} }
private string DownloadFromWebUrl(RemoteBook remoteBook, IIndexer indexer, string torrentUrl) private async Task<string> DownloadFromWebUrl(RemoteBook remoteBook, IIndexer indexer, string torrentUrl)
{ {
byte[] torrentFile = null; byte[] torrentFile = null;
@@ -132,7 +133,7 @@ namespace NzbDrone.Core.Download
request.Headers.Accept = "application/x-bittorrent"; request.Headers.Accept = "application/x-bittorrent";
request.AllowAutoRedirect = false; request.AllowAutoRedirect = false;
var response = _httpClient.Get(request); var response = await _httpClient.GetAsync(request);
if (response.StatusCode == HttpStatusCode.MovedPermanently || if (response.StatusCode == HttpStatusCode.MovedPermanently ||
response.StatusCode == HttpStatusCode.Found || response.StatusCode == HttpStatusCode.Found ||
@@ -151,7 +152,7 @@ namespace NzbDrone.Core.Download
request.Url += new HttpUri(locationHeader); request.Url += new HttpUri(locationHeader);
return DownloadFromWebUrl(remoteBook, indexer, request.Url.ToString()); return await DownloadFromWebUrl(remoteBook, indexer, request.Url.ToString());
} }
throw new WebException("Remote website tried to redirect without providing a location."); throw new WebException("Remote website tried to redirect without providing a location.");
@@ -1,4 +1,5 @@
using System.Net; using System.Net;
using System.Threading.Tasks;
using NLog; using NLog;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
@@ -34,7 +35,7 @@ namespace NzbDrone.Core.Download
protected abstract string AddFromNzbFile(RemoteBook remoteBook, string filename, byte[] fileContent); protected abstract string AddFromNzbFile(RemoteBook remoteBook, string filename, byte[] fileContent);
public override string Download(RemoteBook remoteBook, IIndexer indexer) public override async Task<string> Download(RemoteBook remoteBook, IIndexer indexer)
{ {
var url = remoteBook.Release.DownloadUrl; var url = remoteBook.Release.DownloadUrl;
var filename = FileNameBuilder.CleanFileName(remoteBook.Release.Title) + ".nzb"; var filename = FileNameBuilder.CleanFileName(remoteBook.Release.Title) + ".nzb";
@@ -46,7 +47,9 @@ namespace NzbDrone.Core.Download
var request = indexer?.GetDownloadRequest(url) ?? new HttpRequest(url); var request = indexer?.GetDownloadRequest(url) ?? new HttpRequest(url);
request.RateLimitKey = remoteBook?.Release?.IndexerId.ToString(); request.RateLimitKey = remoteBook?.Release?.IndexerId.ToString();
nzbData = _httpClient.Get(request).ResponseData; var response = await _httpClient.GetAsync(request);
nzbData = response.ResponseData;
_logger.Debug("Downloaded nzb for release '{0}' finished ({1} bytes from {2})", remoteBook.Release.Title, nzbData.Length, url); _logger.Debug("Downloaded nzb for release '{0}' finished ({1} bytes from {2})", remoteBook.Release.Title, nzbData.Length, url);
} }
@@ -346,6 +346,7 @@ namespace NzbDrone.Core.Extras.Metadata
private void DownloadImage(Author author, ImageFileResult image) private void DownloadImage(Author author, ImageFileResult image)
{ {
var fullPath = Path.Combine(author.Path, image.RelativePath); var fullPath = Path.Combine(author.Path, image.RelativePath);
var downloaded = true;
try try
{ {
@@ -353,12 +354,19 @@ namespace NzbDrone.Core.Extras.Metadata
{ {
_httpClient.DownloadFile(image.Url, fullPath); _httpClient.DownloadFile(image.Url, fullPath);
} }
else else if (_diskProvider.FileExists(image.Url))
{ {
_diskProvider.CopyFile(image.Url, fullPath); _diskProvider.CopyFile(image.Url, fullPath);
} }
else
{
downloaded = false;
}
_mediaFileAttributeService.SetFilePermissions(fullPath); if (downloaded)
{
_mediaFileAttributeService.SetFilePermissions(fullPath);
}
} }
catch (WebException ex) catch (WebException ex)
{ {
@@ -0,0 +1,64 @@
using System;
using NLog;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.Localization;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.ThingiProvider.Events;
namespace NzbDrone.Core.HealthCheck.Checks
{
[CheckOn(typeof(ProviderUpdatedEvent<IDownloadClient>))]
[CheckOn(typeof(ProviderDeletedEvent<IDownloadClient>))]
[CheckOn(typeof(ModelEvent<RootFolder>))]
[CheckOn(typeof(ModelEvent<RemotePathMapping>))]
public class DownloadClientRemovesCompletedDownloadsCheck : HealthCheckBase, IProvideHealthCheck
{
private readonly IProvideDownloadClient _downloadClientProvider;
private readonly Logger _logger;
public DownloadClientRemovesCompletedDownloadsCheck(IProvideDownloadClient downloadClientProvider,
Logger logger,
ILocalizationService localizationService)
: base(localizationService)
{
_downloadClientProvider = downloadClientProvider;
_logger = logger;
}
public override HealthCheck Check()
{
var clients = _downloadClientProvider.GetDownloadClients(true);
foreach (var client in clients)
{
try
{
var clientName = client.Definition.Name;
var status = client.GetStatus();
if (status.RemovesCompletedDownloads)
{
return new HealthCheck(GetType(),
HealthCheckResult.Warning,
string.Format(_localizationService.GetLocalizedString("DownloadClientRemovesCompletedDownloadsHealthCheckMessage"), clientName, "Readarr"),
"#download-client-removes-completed-downloads");
}
}
catch (DownloadClientException ex)
{
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
}
catch (Exception ex)
{
_logger.Error(ex, "Unknown error occurred in DownloadClientHistoryRetentionCheck HealthCheck");
}
}
return new HealthCheck(GetType());
}
}
}
@@ -0,0 +1,45 @@
using Dapper;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
{
public class FixMultipleMonitoredEditions : IHousekeepingTask
{
private readonly IMainDatabase _database;
public FixMultipleMonitoredEditions(IMainDatabase database)
{
_database = database;
}
public void Clean()
{
using var mapper = _database.OpenConnection();
if (_database.DatabaseType == DatabaseType.PostgreSQL)
{
mapper.Execute(@"UPDATE ""Editions""
SET ""Monitored"" = true
WHERE ""Id"" IN (
SELECT MIN(""Id"")
FROM ""Editions""
WHERE ""Monitored"" = true
GROUP BY ""BookId""
HAVING COUNT(""BookId"") > 1
)");
}
else
{
mapper.Execute(@"UPDATE ""Editions""
SET ""Monitored"" = 0
WHERE ""Id"" IN (
SELECT MIN(""Id"")
FROM ""Editions""
WHERE ""Monitored"" = 1
GROUP BY ""BookId""
HAVING COUNT(""BookId"") > 1
)");
}
}
}
}
@@ -22,8 +22,8 @@ namespace NzbDrone.Core.IndexerSearch
public void Execute(AuthorSearchCommand message) public void Execute(AuthorSearchCommand message)
{ {
var decisions = _releaseSearchService.AuthorSearch(message.AuthorId, false, message.Trigger == CommandTrigger.Manual, false); var decisions = _releaseSearchService.AuthorSearch(message.AuthorId, false, message.Trigger == CommandTrigger.Manual, false).GetAwaiter().GetResult();
var processed = _processDownloadDecisions.ProcessDecisions(decisions); var processed = _processDownloadDecisions.ProcessDecisions(decisions).GetAwaiter().GetResult();
_logger.ProgressInfo("Author search completed. {0} reports downloaded.", processed.Grabbed.Count); _logger.ProgressInfo("Author search completed. {0} reports downloaded.", processed.Grabbed.Count);
} }
@@ -2,11 +2,11 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Threading.Tasks;
using NLog; using NLog;
using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Books; using NzbDrone.Core.Books;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Queue; using NzbDrone.Core.Queue;
@@ -39,16 +39,15 @@ namespace NzbDrone.Core.IndexerSearch
_logger = logger; _logger = logger;
} }
private void SearchForMissingBooks(List<Book> books, bool userInvokedSearch) private async Task SearchForMissingBooks(List<Book> books, bool userInvokedSearch)
{ {
_logger.ProgressInfo("Performing missing search for {0} books", books.Count); _logger.ProgressInfo("Performing missing search for {0} books", books.Count);
var downloadedCount = 0; var downloadedCount = 0;
foreach (var book in books) foreach (var book in books)
{ {
List<DownloadDecision> decisions; var decisions = await _releaseSearchService.BookSearch(book.Id, false, userInvokedSearch, false);
decisions = _releaseSearchService.BookSearch(book.Id, false, userInvokedSearch, false); var processed = await _processDownloadDecisions.ProcessDecisions(decisions);
var processed = _processDownloadDecisions.ProcessDecisions(decisions);
downloadedCount += processed.Grabbed.Count; downloadedCount += processed.Grabbed.Count;
} }
@@ -60,9 +59,8 @@ namespace NzbDrone.Core.IndexerSearch
{ {
foreach (var bookId in message.BookIds) foreach (var bookId in message.BookIds)
{ {
var decisions = var decisions = _releaseSearchService.BookSearch(bookId, false, message.Trigger == CommandTrigger.Manual, false).GetAwaiter().GetResult();
_releaseSearchService.BookSearch(bookId, false, message.Trigger == CommandTrigger.Manual, false); var processed = _processDownloadDecisions.ProcessDecisions(decisions).GetAwaiter().GetResult();
var processed = _processDownloadDecisions.ProcessDecisions(decisions);
_logger.ProgressInfo("Book search completed. {0} reports downloaded.", processed.Grabbed.Count); _logger.ProgressInfo("Book search completed. {0} reports downloaded.", processed.Grabbed.Count);
} }
@@ -106,7 +104,7 @@ namespace NzbDrone.Core.IndexerSearch
var queue = _queueService.GetQueue().Where(q => q.Book != null).Select(q => q.Book.Id); var queue = _queueService.GetQueue().Where(q => q.Book != null).Select(q => q.Book.Id);
var missing = books.Where(e => !queue.Contains(e.Id)).ToList(); var missing = books.Where(e => !queue.Contains(e.Id)).ToList();
SearchForMissingBooks(missing, message.Trigger == CommandTrigger.Manual); SearchForMissingBooks(missing, message.Trigger == CommandTrigger.Manual).GetAwaiter().GetResult();
} }
public void Execute(CutoffUnmetBookSearchCommand message) public void Execute(CutoffUnmetBookSearchCommand message)
@@ -132,7 +130,7 @@ namespace NzbDrone.Core.IndexerSearch
var queue = _queueService.GetQueue().Where(q => q.Book != null).Select(q => q.Book.Id); var queue = _queueService.GetQueue().Where(q => q.Book != null).Select(q => q.Book.Id);
var missing = books.Where(e => !queue.Contains(e.Id)).ToList(); var missing = books.Where(e => !queue.Contains(e.Id)).ToList();
SearchForMissingBooks(missing, message.Trigger == CommandTrigger.Manual); SearchForMissingBooks(missing, message.Trigger == CommandTrigger.Manual).GetAwaiter().GetResult();
} }
} }
} }
@@ -5,7 +5,6 @@ using System.Threading.Tasks;
using NLog; using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Common.TPL;
using NzbDrone.Core.Books; using NzbDrone.Core.Books;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
@@ -16,8 +15,8 @@ namespace NzbDrone.Core.IndexerSearch
{ {
public interface ISearchForReleases public interface ISearchForReleases
{ {
List<DownloadDecision> BookSearch(int bookId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch); Task<List<DownloadDecision>> BookSearch(int bookId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch);
List<DownloadDecision> AuthorSearch(int authorId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch); Task<List<DownloadDecision>> AuthorSearch(int authorId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch);
} }
public class ReleaseSearchService : ISearchForReleases public class ReleaseSearchService : ISearchForReleases
@@ -41,31 +40,31 @@ namespace NzbDrone.Core.IndexerSearch
_logger = logger; _logger = logger;
} }
public List<DownloadDecision> BookSearch(int bookId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch) public async Task<List<DownloadDecision>> BookSearch(int bookId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch)
{ {
var downloadDecisions = new List<DownloadDecision>(); var downloadDecisions = new List<DownloadDecision>();
var book = _bookService.GetBook(bookId); var book = _bookService.GetBook(bookId);
var decisions = BookSearch(book, missingOnly, userInvokedSearch, interactiveSearch); var decisions = await BookSearch(book, missingOnly, userInvokedSearch, interactiveSearch);
downloadDecisions.AddRange(decisions); downloadDecisions.AddRange(decisions);
return DeDupeDecisions(downloadDecisions); return DeDupeDecisions(downloadDecisions);
} }
public List<DownloadDecision> AuthorSearch(int authorId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch) public async Task<List<DownloadDecision>> AuthorSearch(int authorId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch)
{ {
var downloadDecisions = new List<DownloadDecision>(); var downloadDecisions = new List<DownloadDecision>();
var author = _authorService.GetAuthor(authorId); var author = _authorService.GetAuthor(authorId);
var decisions = AuthorSearch(author, missingOnly, userInvokedSearch, interactiveSearch); var decisions = await AuthorSearch(author, missingOnly, userInvokedSearch, interactiveSearch);
downloadDecisions.AddRange(decisions); downloadDecisions.AddRange(decisions);
return DeDupeDecisions(downloadDecisions); return DeDupeDecisions(downloadDecisions);
} }
public List<DownloadDecision> AuthorSearch(Author author, bool missingOnly, bool userInvokedSearch, bool interactiveSearch) public async Task<List<DownloadDecision>> AuthorSearch(Author author, bool missingOnly, bool userInvokedSearch, bool interactiveSearch)
{ {
var searchSpec = Get<AuthorSearchCriteria>(author, userInvokedSearch, interactiveSearch); var searchSpec = Get<AuthorSearchCriteria>(author, userInvokedSearch, interactiveSearch);
var books = _bookService.GetBooksByAuthor(author.Id); var books = _bookService.GetBooksByAuthor(author.Id);
@@ -74,10 +73,10 @@ namespace NzbDrone.Core.IndexerSearch
searchSpec.Books = books; searchSpec.Books = books;
return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); return await Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
} }
public List<DownloadDecision> BookSearch(Book book, bool missingOnly, bool userInvokedSearch, bool interactiveSearch) public async Task<List<DownloadDecision>> BookSearch(Book book, bool missingOnly, bool userInvokedSearch, bool interactiveSearch)
{ {
var author = _authorService.GetAuthor(book.AuthorId); var author = _authorService.GetAuthor(book.AuthorId);
@@ -91,7 +90,7 @@ namespace NzbDrone.Core.IndexerSearch
searchSpec.BookYear = book.ReleaseDate.Value.Year; searchSpec.BookYear = book.ReleaseDate.Value.Year;
} }
return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); return await Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
} }
private TSpec Get<TSpec>(Author author, List<Book> books, bool userInvokedSearch, bool interactiveSearch) private TSpec Get<TSpec>(Author author, List<Book> books, bool userInvokedSearch, bool interactiveSearch)
@@ -118,7 +117,7 @@ namespace NzbDrone.Core.IndexerSearch
return spec; return spec;
} }
private List<DownloadDecision> Dispatch(Func<IIndexer, IEnumerable<ReleaseInfo>> searchAction, SearchCriteriaBase criteriaBase) private async Task<List<DownloadDecision>> Dispatch(Func<IIndexer, Task<IList<ReleaseInfo>>> searchAction, SearchCriteriaBase criteriaBase)
{ {
var indexers = criteriaBase.InteractiveSearch ? var indexers = criteriaBase.InteractiveSearch ?
_indexerFactory.InteractiveSearchEnabled() : _indexerFactory.InteractiveSearchEnabled() :
@@ -127,42 +126,33 @@ namespace NzbDrone.Core.IndexerSearch
// Filter indexers to untagged indexers and indexers with intersecting tags // Filter indexers to untagged indexers and indexers with intersecting tags
indexers = indexers.Where(i => i.Definition.Tags.Empty() || i.Definition.Tags.Intersect(criteriaBase.Author.Tags).Any()).ToList(); indexers = indexers.Where(i => i.Definition.Tags.Empty() || i.Definition.Tags.Intersect(criteriaBase.Author.Tags).Any()).ToList();
var reports = new List<ReleaseInfo>();
_logger.ProgressInfo("Searching indexers for {0}. {1} active indexers", criteriaBase, indexers.Count); _logger.ProgressInfo("Searching indexers for {0}. {1} active indexers", criteriaBase, indexers.Count);
var taskList = new List<Task>(); var tasks = indexers.Select(indexer => DispatchIndexer(searchAction, indexer, criteriaBase));
var taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None);
foreach (var indexer in indexers) var batch = await Task.WhenAll(tasks);
{
var indexerLocal = indexer;
taskList.Add(taskFactory.StartNew(() => var reports = batch.SelectMany(x => x).ToList();
{
try
{
var indexerReports = searchAction(indexerLocal);
lock (reports)
{
reports.AddRange(indexerReports);
}
}
catch (Exception e)
{
_logger.Error(e, "Error while searching for {0}", criteriaBase);
}
}).LogExceptions());
}
Task.WaitAll(taskList.ToArray());
_logger.Debug("Total of {0} reports were found for {1} from {2} indexers", reports.Count, criteriaBase, indexers.Count); _logger.Debug("Total of {0} reports were found for {1} from {2} indexers", reports.Count, criteriaBase, indexers.Count);
return _makeDownloadDecision.GetSearchDecision(reports, criteriaBase).ToList(); return _makeDownloadDecision.GetSearchDecision(reports, criteriaBase).ToList();
} }
private async Task<IList<ReleaseInfo>> DispatchIndexer(Func<IIndexer, Task<IList<ReleaseInfo>>> searchAction, IIndexer indexer, SearchCriteriaBase criteriaBase)
{
try
{
return await searchAction(indexer);
}
catch (Exception ex)
{
_logger.Error(ex, "Error while searching for {0}", criteriaBase);
}
return Array.Empty<ReleaseInfo>();
}
private List<DownloadDecision> DeDupeDecisions(List<DownloadDecision> decisions) private List<DownloadDecision> DeDupeDecisions(List<DownloadDecision> decisions)
{ {
// De-dupe reports by guid so duplicate results aren't returned. Pick the one with the least rejections and higher indexer priority. // De-dupe reports by guid so duplicate results aren't returned. Pick the one with the least rejections and higher indexer priority.
@@ -3,13 +3,12 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using NLog; using NLog;
using NzbDrone.Common.TPL;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers namespace NzbDrone.Core.Indexers
{ {
public interface IFetchAndParseRss public interface IFetchAndParseRss
{ {
List<ReleaseInfo> Fetch(); Task<List<ReleaseInfo>> Fetch();
} }
public class FetchAndParseRssService : IFetchAndParseRss public class FetchAndParseRssService : IFetchAndParseRss
@@ -23,54 +22,42 @@ namespace NzbDrone.Core.Indexers
_logger = logger; _logger = logger;
} }
public List<ReleaseInfo> Fetch() public async Task<List<ReleaseInfo>> Fetch()
{ {
var result = new List<ReleaseInfo>();
var indexers = _indexerFactory.RssEnabled(); var indexers = _indexerFactory.RssEnabled();
if (!indexers.Any()) if (!indexers.Any())
{ {
_logger.Warn("No available indexers. check your configuration."); _logger.Warn("No available indexers. check your configuration.");
return result;
return new List<ReleaseInfo>();
} }
_logger.Debug("Available indexers {0}", indexers.Count); _logger.Debug("Available indexers {0}", indexers.Count);
var taskList = new List<Task>(); var tasks = indexers.Select(FetchIndexer);
var taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None);
foreach (var indexer in indexers) var batch = await Task.WhenAll(tasks);
{
var indexerLocal = indexer;
var task = taskFactory.StartNew(() => var result = batch.SelectMany(x => x).ToList();
{
try
{
var indexerReports = indexerLocal.FetchRecent();
lock (result)
{
_logger.Debug("Found {0} from {1}", indexerReports.Count, indexer.Name);
result.AddRange(indexerReports);
}
}
catch (Exception e)
{
_logger.Error(e, "Error during RSS Sync");
}
}).LogExceptions();
taskList.Add(task);
}
Task.WaitAll(taskList.ToArray());
_logger.Debug("Found {0} reports", result.Count); _logger.Debug("Found {0} reports", result.Count);
return result; return result;
} }
private async Task<IList<ReleaseInfo>> FetchIndexer(IIndexer indexer)
{
try
{
return await indexer.FetchRecent();
}
catch (Exception ex)
{
_logger.Error(ex, "Error during RSS Sync");
}
return Array.Empty<ReleaseInfo>();
}
} }
} }
@@ -23,7 +23,11 @@ namespace NzbDrone.Core.Indexers.FileList
{ {
var pageableRequests = new IndexerPageableRequestChain(); var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest("search-torrents", Settings.Categories, string.Format("&type=name&query={0}+{1}", Uri.EscapeDataString(searchCriteria.AuthorQuery.Trim()), Uri.EscapeDataString(searchCriteria.BookQuery.Trim())))); var authorQuery = searchCriteria.AuthorQuery.Replace("+", " ").Trim();
var bookQuery = searchCriteria.BookQuery.Replace("+", " ").Trim();
pageableRequests.Add(GetRequest("search-torrents", Settings.Categories, string.Format("&type=name&query={0}+{1}", Uri.EscapeDataString(authorQuery), Uri.EscapeDataString(bookQuery))));
pageableRequests.Add(GetRequest("search-torrents", Settings.Categories, string.Format("&type=name&query={0}+{1}", Uri.EscapeDataString(bookQuery), Uri.EscapeDataString(authorQuery))));
return pageableRequests; return pageableRequests;
} }
@@ -32,7 +36,9 @@ namespace NzbDrone.Core.Indexers.FileList
{ {
var pageableRequests = new IndexerPageableRequestChain(); var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest("search-torrents", Settings.Categories, string.Format("&type=name&query={0}", Uri.EscapeDataString(searchCriteria.AuthorQuery.Trim())))); var authorQuery = searchCriteria.AuthorQuery.Replace("+", " ").Trim();
pageableRequests.Add(GetRequest("search-torrents", Settings.Categories, string.Format("&type=name&query={0}", Uri.EscapeDataString(authorQuery))));
return pageableRequests; return pageableRequests;
} }
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Indexers.FileList
Categories = new int[] Categories = new int[]
{ {
(int)FileListCategories.DOCS (int)FileListCategories.Docs
}; };
} }
@@ -61,7 +61,7 @@ namespace NzbDrone.Core.Indexers.FileList
public enum FileListCategories public enum FileListCategories
{ {
[FieldOption] [FieldOption(Label = "Docs")]
DOCS = 16, Docs = 16,
} }
} }
@@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using FluentValidation.Results; using FluentValidation.Results;
using NLog; using NLog;
using NzbDrone.Common.Cache; using NzbDrone.Common.Cache;
@@ -68,11 +69,12 @@ namespace NzbDrone.Core.Indexers.Gazelle
return settings; return settings;
} }
protected override void Test(List<ValidationFailure> failures) protected override async Task Test(List<ValidationFailure> failures)
{ {
// Remove previous cookies when testing incase user or pwd change // Remove previous cookies when testing incase user or pwd change
_authCookieCache.Remove(Settings.BaseUrl.Trim().TrimEnd('/')); _authCookieCache.Remove(Settings.BaseUrl.Trim().TrimEnd('/'));
base.Test(failures);
await base.Test(failures);
} }
} }
} }
@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
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.Extensions; using NzbDrone.Common.Extensions;
@@ -43,17 +44,14 @@ namespace NzbDrone.Core.Indexers.Gazelle
private IEnumerable<IndexerRequest> GetRequest(string searchParameters) private IEnumerable<IndexerRequest> GetRequest(string searchParameters)
{ {
Authenticate(); Authenticate().GetAwaiter().GetResult();
var filter = ""; var filter = "";
if (searchParameters == null) if (searchParameters == null)
{ {
} }
var request = var request = new IndexerRequest($"{Settings.BaseUrl.Trim().TrimEnd('/')}/ajax.php?action=browse&searchstr={searchParameters}{filter}", HttpAccept.Json);
new IndexerRequest(
$"{Settings.BaseUrl.Trim().TrimEnd('/')}/ajax.php?action=browse&searchstr={searchParameters}{filter}",
HttpAccept.Json);
var cookies = AuthCookieCache.Find(Settings.BaseUrl.Trim().TrimEnd('/')); var cookies = AuthCookieCache.Find(Settings.BaseUrl.Trim().TrimEnd('/'));
foreach (var cookie in cookies) foreach (var cookie in cookies)
@@ -64,36 +62,36 @@ namespace NzbDrone.Core.Indexers.Gazelle
yield return request; yield return request;
} }
private GazelleAuthResponse GetIndex(Dictionary<string, string> cookies) private async Task<GazelleAuthResponse> GetIndex(Dictionary<string, string> cookies)
{ {
var indexRequestBuilder = new HttpRequestBuilder($"{Settings.BaseUrl.Trim().TrimEnd('/')}") var indexRequestBuilder = new HttpRequestBuilder($"{Settings.BaseUrl.Trim().TrimEnd('/')}")
{ {
LogResponseContent = true LogResponseContent = true,
Method = HttpMethod.Post
}; };
indexRequestBuilder.SetCookies(cookies); indexRequestBuilder.SetCookies(cookies);
indexRequestBuilder.Method = HttpMethod.Post;
indexRequestBuilder.Resource("ajax.php?action=index"); indexRequestBuilder.Resource("ajax.php?action=index");
var authIndexRequest = indexRequestBuilder var authIndexRequest = indexRequestBuilder
.Accept(HttpAccept.Json) .Accept(HttpAccept.Json)
.Build(); .Build();
var indexResponse = HttpClient.Execute(authIndexRequest); var indexResponse = await HttpClient.ExecuteAsync(authIndexRequest);
var result = Json.Deserialize<GazelleAuthResponse>(indexResponse.Content); var result = Json.Deserialize<GazelleAuthResponse>(indexResponse.Content);
return result; return result;
} }
private void Authenticate() private async Task Authenticate()
{ {
var requestBuilder = new HttpRequestBuilder($"{Settings.BaseUrl.Trim().TrimEnd('/')}") var requestBuilder = new HttpRequestBuilder($"{Settings.BaseUrl.Trim().TrimEnd('/')}")
{ {
LogResponseContent = true LogResponseContent = true,
Method = HttpMethod.Post
}; };
requestBuilder.Method = HttpMethod.Post;
requestBuilder.Resource("login.php"); requestBuilder.Resource("login.php");
requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15); requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15);
@@ -111,19 +109,20 @@ namespace NzbDrone.Core.Indexers.Gazelle
.Accept(HttpAccept.Json) .Accept(HttpAccept.Json)
.Build(); .Build();
var response = HttpClient.Execute(authLoginRequest); var response = await HttpClient.ExecuteAsync(authLoginRequest);
cookies = response.GetCookies(); cookies = response.GetCookies();
AuthCookieCache.Set(authKey, cookies); AuthCookieCache.Set(authKey, cookies);
} }
var index = GetIndex(cookies); var index = await GetIndex(cookies);
if (index == null || index.Status.IsNullOrWhiteSpace() || index.Status != "success") if (index == null || index.Status.IsNullOrWhiteSpace() || index.Status != "success")
{ {
Logger.Debug("Gazelle authentication failed."); Logger.Debug("Gazelle authentication failed.");
AuthCookieCache.Remove(authKey); AuthCookieCache.Remove(authKey);
throw new Exception("Failed to authenticate with Gazelle."); throw new Exception("Failed to authenticate with Gazelle.");
} }
+27 -17
View File
@@ -40,31 +40,31 @@ namespace NzbDrone.Core.Indexers
_httpClient = httpClient; _httpClient = httpClient;
} }
public override IList<ReleaseInfo> FetchRecent() public override Task<IList<ReleaseInfo>> FetchRecent()
{ {
if (!SupportsRss) if (!SupportsRss)
{ {
return new List<ReleaseInfo>(); return Task.FromResult<IList<ReleaseInfo>>(Array.Empty<ReleaseInfo>());
} }
return FetchReleases(g => g.GetRecentRequests(), true); return FetchReleases(g => g.GetRecentRequests(), true);
} }
public override IList<ReleaseInfo> Fetch(BookSearchCriteria searchCriteria) public override Task<IList<ReleaseInfo>> Fetch(BookSearchCriteria searchCriteria)
{ {
if (!SupportsSearch) if (!SupportsSearch)
{ {
return new List<ReleaseInfo>(); return Task.FromResult<IList<ReleaseInfo>>(Array.Empty<ReleaseInfo>());
} }
return FetchReleases(g => g.GetSearchRequests(searchCriteria)); return FetchReleases(g => g.GetSearchRequests(searchCriteria));
} }
public override IList<ReleaseInfo> Fetch(AuthorSearchCriteria searchCriteria) public override Task<IList<ReleaseInfo>> Fetch(AuthorSearchCriteria searchCriteria)
{ {
if (!SupportsSearch) if (!SupportsSearch)
{ {
return new List<ReleaseInfo>(); return Task.FromResult<IList<ReleaseInfo>>(Array.Empty<ReleaseInfo>());
} }
return FetchReleases(g => g.GetSearchRequests(searchCriteria)); return FetchReleases(g => g.GetSearchRequests(searchCriteria));
@@ -75,7 +75,7 @@ namespace NzbDrone.Core.Indexers
return new HttpRequest(link); return new HttpRequest(link);
} }
protected virtual IList<ReleaseInfo> FetchReleases(Func<IIndexerRequestGenerator, IndexerPageableRequestChain> pageableRequestChainSelector, bool isRecent = false) protected virtual async Task<IList<ReleaseInfo>> FetchReleases(Func<IIndexerRequestGenerator, IndexerPageableRequestChain> pageableRequestChainSelector, bool isRecent = false)
{ {
var releases = new List<ReleaseInfo>(); var releases = new List<ReleaseInfo>();
var url = string.Empty; var url = string.Empty;
@@ -107,7 +107,7 @@ namespace NzbDrone.Core.Indexers
{ {
url = request.Url.FullUri; url = request.Url.FullUri;
var page = FetchPage(request, parser); var page = await FetchPage(request, parser);
pagedReleases.AddRange(page); pagedReleases.AddRange(page);
@@ -257,9 +257,17 @@ namespace NzbDrone.Core.Indexers
protected virtual bool IsValidRelease(ReleaseInfo release) protected virtual bool IsValidRelease(ReleaseInfo release)
{ {
if (release.Title.IsNullOrWhiteSpace())
{
_logger.Trace("Invalid Release: '{0}' from indexer: {1}. No title provided.", release.InfoUrl, Definition.Name);
return false;
}
if (release.DownloadUrl.IsNullOrWhiteSpace()) if (release.DownloadUrl.IsNullOrWhiteSpace())
{ {
_logger.Trace("Invalid Release: '{0}' from indexer: {1}. No Download URL provided.", release.Title, release.Indexer); _logger.Trace("Invalid Release: '{0}' from indexer: {1}. No Download URL provided.", release.Title, Definition.Name);
return false; return false;
} }
@@ -271,9 +279,9 @@ namespace NzbDrone.Core.Indexers
return PageSize != 0 && page.Count >= PageSize; return PageSize != 0 && page.Count >= PageSize;
} }
protected virtual IList<ReleaseInfo> FetchPage(IndexerRequest request, IParseIndexerResponse parser) protected virtual async Task<IList<ReleaseInfo>> FetchPage(IndexerRequest request, IParseIndexerResponse parser)
{ {
var response = FetchIndexerResponse(request); var response = await FetchIndexerResponse(request);
try try
{ {
@@ -287,7 +295,7 @@ namespace NzbDrone.Core.Indexers
} }
} }
protected virtual IndexerResponse FetchIndexerResponse(IndexerRequest request) protected virtual async Task<IndexerResponse> FetchIndexerResponse(IndexerRequest request)
{ {
_logger.Debug("Downloading Feed " + request.HttpRequest.ToString(false)); _logger.Debug("Downloading Feed " + request.HttpRequest.ToString(false));
@@ -298,15 +306,17 @@ namespace NzbDrone.Core.Indexers
request.HttpRequest.RateLimitKey = Definition.Id.ToString(); request.HttpRequest.RateLimitKey = Definition.Id.ToString();
return new IndexerResponse(request, _httpClient.Execute(request.HttpRequest)); var response = await _httpClient.ExecuteAsync(request.HttpRequest);
return new IndexerResponse(request, response);
} }
protected override void Test(List<ValidationFailure> failures) protected override async Task Test(List<ValidationFailure> failures)
{ {
failures.AddIfNotNull(TestConnection()); failures.AddIfNotNull(await TestConnection());
} }
protected virtual ValidationFailure TestConnection() protected virtual async Task<ValidationFailure> TestConnection()
{ {
try try
{ {
@@ -319,7 +329,7 @@ namespace NzbDrone.Core.Indexers
return new ValidationFailure(string.Empty, "No rss feed query available. This may be an issue with the indexer or your indexer category settings."); return new ValidationFailure(string.Empty, "No rss feed query available. This may be an issue with the indexer or your indexer category settings.");
} }
var releases = FetchPage(firstRequest, parser); var releases = await FetchPage(firstRequest, parser);
if (releases.Empty()) if (releases.Empty())
{ {
+4 -3
View File
@@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@@ -12,9 +13,9 @@ namespace NzbDrone.Core.Indexers
bool SupportsSearch { get; } bool SupportsSearch { get; }
DownloadProtocol Protocol { get; } DownloadProtocol Protocol { get; }
IList<ReleaseInfo> FetchRecent(); Task<IList<ReleaseInfo>> FetchRecent();
IList<ReleaseInfo> Fetch(BookSearchCriteria searchCriteria); Task<IList<ReleaseInfo>> Fetch(BookSearchCriteria searchCriteria);
IList<ReleaseInfo> Fetch(AuthorSearchCriteria searchCriteria); Task<IList<ReleaseInfo>> Fetch(AuthorSearchCriteria searchCriteria);
HttpRequest GetDownloadRequest(string link); HttpRequest GetDownloadRequest(string link);
} }
} }
+6 -7
View File
@@ -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 FluentValidation.Results; using FluentValidation.Results;
using NLog; using NLog;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
@@ -66,16 +67,14 @@ namespace NzbDrone.Core.Indexers
protected TSettings Settings => (TSettings)Definition.Settings; protected TSettings Settings => (TSettings)Definition.Settings;
public abstract IList<ReleaseInfo> FetchRecent(); public abstract Task<IList<ReleaseInfo>> FetchRecent();
public abstract Task<IList<ReleaseInfo>> Fetch(BookSearchCriteria searchCriteria);
public abstract IList<ReleaseInfo> Fetch(BookSearchCriteria searchCriteria); public abstract Task<IList<ReleaseInfo>> Fetch(AuthorSearchCriteria searchCriteria);
public abstract IList<ReleaseInfo> Fetch(AuthorSearchCriteria searchCriteria);
public abstract HttpRequest GetDownloadRequest(string link); public abstract HttpRequest GetDownloadRequest(string link);
protected virtual IList<ReleaseInfo> CleanupReleases(IEnumerable<ReleaseInfo> releases) protected virtual IList<ReleaseInfo> CleanupReleases(IEnumerable<ReleaseInfo> releases)
{ {
var result = releases.DistinctBy(v => v.Guid).ToList(); var result = releases.DistinctBy(v => v.Guid).ToList();
var settings = Definition.Settings as IIndexerSettings;
result.ForEach(c => result.ForEach(c =>
{ {
@@ -94,7 +93,7 @@ namespace NzbDrone.Core.Indexers
try try
{ {
Test(failures); Test(failures).GetAwaiter().GetResult();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -105,7 +104,7 @@ namespace NzbDrone.Core.Indexers
return new ValidationResult(failures); return new ValidationResult(failures);
} }
protected abstract void Test(List<ValidationFailure> failures); protected abstract Task Test(List<ValidationFailure> failures);
public override string ToString() public override string ToString()
{ {
@@ -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 FluentValidation.Results; using FluentValidation.Results;
using NLog; using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
@@ -92,9 +93,10 @@ namespace NzbDrone.Core.Indexers.Newznab
return settings; return settings;
} }
protected override void Test(List<ValidationFailure> failures) protected override async Task Test(List<ValidationFailure> failures)
{ {
base.Test(failures); await base.Test(failures);
if (failures.HasErrors()) if (failures.HasErrors())
{ {
return; return;
@@ -7,6 +7,7 @@ using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer; using NzbDrone.Common.Serializer;
using NzbDrone.Core.Indexers.Exceptions;
namespace NzbDrone.Core.Indexers.Newznab namespace NzbDrone.Core.Indexers.Newznab
{ {
@@ -73,6 +74,13 @@ namespace NzbDrone.Core.Indexers.Newznab
_logger.Debug(ex, "Failed to parse newznab api capabilities for {0}", indexerSettings.BaseUrl); _logger.Debug(ex, "Failed to parse newznab api capabilities for {0}", indexerSettings.BaseUrl);
throw; throw;
} }
catch (ApiKeyException ex)
{
ex.WithData(response, 128 * 1024);
_logger.Trace("Unexpected Response content ({0} bytes): {1}", response.ResponseData.Length, response.Content);
_logger.Debug(ex, "Failed to parse newznab api capabilities for {0}, invalid API key", indexerSettings.BaseUrl);
throw;
}
catch (Exception ex) catch (Exception ex)
{ {
ex.WithData(response, 128 * 1024); ex.WithData(response, 128 * 1024);
+5 -4
View File
@@ -1,4 +1,5 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using NLog; using NLog;
using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
@@ -39,16 +40,16 @@ namespace NzbDrone.Core.Indexers
_logger = logger; _logger = logger;
} }
private ProcessedDecisions Sync() private async Task<ProcessedDecisions> Sync()
{ {
_logger.ProgressInfo("Starting RSS Sync"); _logger.ProgressInfo("Starting RSS Sync");
var rssReleases = _rssFetcherAndParser.Fetch(); var rssReleases = await _rssFetcherAndParser.Fetch();
var pendingReleases = _pendingReleaseService.GetPending(); var pendingReleases = _pendingReleaseService.GetPending();
var reports = rssReleases.Concat(pendingReleases).ToList(); var reports = rssReleases.Concat(pendingReleases).ToList();
var decisions = _downloadDecisionMaker.GetRssDecision(reports); var decisions = _downloadDecisionMaker.GetRssDecision(reports);
var processed = _processDownloadDecisions.ProcessDecisions(decisions); var processed = await _processDownloadDecisions.ProcessDecisions(decisions);
var message = string.Format("RSS Sync Completed. Reports found: {0}, Reports grabbed: {1}", reports.Count, processed.Grabbed.Count); var message = string.Format("RSS Sync Completed. Reports found: {0}, Reports grabbed: {1}", reports.Count, processed.Grabbed.Count);
@@ -64,7 +65,7 @@ namespace NzbDrone.Core.Indexers
public void Execute(RssSyncCommand message) public void Execute(RssSyncCommand message)
{ {
var processed = Sync(); var processed = Sync().GetAwaiter().GetResult();
var grabbedOrPending = processed.Grabbed.Concat(processed.Pending).ToList(); var grabbedOrPending = processed.Grabbed.Concat(processed.Pending).ToList();
_eventAggregator.PublishEvent(new RssSyncCompleteEvent(processed)); _eventAggregator.PublishEvent(new RssSyncCompleteEvent(processed));
@@ -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 FluentValidation.Results; using FluentValidation.Results;
using NLog; using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
@@ -69,9 +70,10 @@ namespace NzbDrone.Core.Indexers.Torznab
return settings; return settings;
} }
protected override void Test(List<ValidationFailure> failures) protected override async Task Test(List<ValidationFailure> failures)
{ {
base.Test(failures); await base.Test(failures);
if (failures.HasErrors()) if (failures.HasErrors())
{ {
return; return;
+3 -1
View File
@@ -626,5 +626,7 @@
"AddNew": "Добави нов", "AddNew": "Добави нов",
"NextExecution": "Следващо изпълнение", "NextExecution": "Следващо изпълнение",
"AllResultsAreHiddenByTheAppliedFilter": "Всички резултати са скрити от приложения филтър", "AllResultsAreHiddenByTheAppliedFilter": "Всички резултати са скрити от приложения филтър",
"Backup": "Архивиране" "Backup": "Архивиране",
"MetadataProfiles": "Добави профил на метадата",
"MetadataProfile": "Добави профил на метадата"
} }
+73 -41
View File
@@ -13,36 +13,36 @@
"45MinutesFourtyFive": "90 minut: {0}", "45MinutesFourtyFive": "90 minut: {0}",
"60MinutesSixty": "60 minut: {0}", "60MinutesSixty": "60 minut: {0}",
"APIKey": "Klíč API", "APIKey": "Klíč API",
"About": "O", "About": "O aplikaci",
"AddListExclusion": "Přidat vyloučení seznamu", "AddListExclusion": "Přidat vyloučení seznamu",
"AddingTag": "Přidání značky", "AddingTag": "Přidání značky",
"AgeWhenGrabbed": "Stáří (při zachycení)", "AgeWhenGrabbed": "Stáří (kdy bylo získáno)",
"AlreadyInYourLibrary": "Již ve vaší knihovně", "AlreadyInYourLibrary": "Již máte ve své knihovně",
"AlternateTitles": "Alternativní název", "AlternateTitles": "Alternativní název",
"Analytics": "Analytics", "Analytics": "Analýzy",
"AnalyticsEnabledHelpText": "Odesílejte anonymní informace o použití a chybách na servery Radarru. To zahrnuje informace o vašem prohlížeči, které stránky Radarr WebUI používáte, hlášení chyb a také verzi operačního systému a běhového prostředí. Tyto informace použijeme k upřednostnění funkcí a oprav chyb.", "AnalyticsEnabledHelpText": "Odesílejte anonymní informace o použití a chybách na servery Radarru. To zahrnuje informace o vašem prohlížeči, které stránky Radarr WebUI používáte, hlášení chyb a také verzi operačního systému a běhového prostředí. Tyto informace použijeme k upřednostnění funkcí a oprav chyb.",
"AppDataDirectory": "Adresář AppData", "AppDataDirectory": "Adresář AppData",
"ApplyTags": "Použít značky", "ApplyTags": "Použít značky",
"Authentication": "Ověření", "Authentication": "Ověřování",
"AuthenticationMethodHelpText": "Vyžadovat uživatelské jméno a heslo pro přístup k Radarr", "AuthenticationMethodHelpText": "Vyžadovat uživatelské jméno a heslo pro přístup k Radarr",
"AuthorClickToChangeBook": "Kliknutím změníte film", "AuthorClickToChangeBook": "Kliknutím změníte film",
"AutoRedownloadFailedHelpText": "Automaticky vyhledejte a pokuste se stáhnout jiné vydání", "AutoRedownloadFailedHelpText": "Automatické vyhledání a pokus o stažení jiného vydání",
"AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Filmy odstraněné z disku jsou automaticky sledovány v Radarru", "AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Filmy odstraněné z disku jsou automaticky sledovány v Radarru",
"Automatic": "Automatický", "Automatic": "Automatický",
"BackupFolderHelpText": "Relativní cesty budou v adresáři AppData společnosti Radarr", "BackupFolderHelpText": "Relativní cesty budou v adresáři AppData společnosti Radarr",
"BackupNow": "Zálohovat hned", "BackupNow": "Ihned zálohovat",
"BackupRetentionHelpText": "Automatické zálohy starší než doba uchování budou automaticky vyčištěny", "BackupRetentionHelpText": "Automatické zálohy starší než doba uchování budou automaticky vyčištěny",
"Backups": "Zálohy", "Backups": "Zálohy",
"BindAddress": "Vazba adresy", "BindAddress": "Vázat adresu",
"BindAddressHelpText": "Platná adresa IP4 nebo '*' pro všechna rozhraní", "BindAddressHelpText": "Platná IP adresa, localhost nebo '*' pro všechna rozhraní",
"BindAddressHelpTextWarning": "Vyžaduje restart, aby se projevilo", "BindAddressHelpTextWarning": "Vyžaduje restart, aby se projevilo",
"BookIsDownloading": "Film se stahuje", "BookIsDownloading": "Film se stahuje",
"BookIsDownloadingInterp": "Film se stahuje - {0}% {1}", "BookIsDownloadingInterp": "Film se stahuje - {0}% {1}",
"Branch": "Větev", "Branch": "Větev",
"BypassProxyForLocalAddresses": "Obejít proxy pro místní adresy", "BypassProxyForLocalAddresses": "Obcházení proxy serveru pro místní adresy",
"Calendar": "Kalendář", "Calendar": "Kalendář",
"CalendarWeekColumnHeaderHelpText": "Zobrazuje se nad každým sloupcem, když je aktivní zobrazení týden", "CalendarWeekColumnHeaderHelpText": "Zobrazuje se nad každým sloupcem, když je aktivní zobrazení týden",
"Cancel": "zrušení", "Cancel": "Zrušit",
"CancelMessageText": "Opravdu chcete zrušit tento nevyřízený úkol?", "CancelMessageText": "Opravdu chcete zrušit tento nevyřízený úkol?",
"CertificateValidation": "Ověření certifikátu", "CertificateValidation": "Ověření certifikátu",
"CertificateValidationHelpText": "Změňte přísnost ověřování certifikátů HTTPS. Neměňte, pokud nerozumíte rizikům.", "CertificateValidationHelpText": "Změňte přísnost ověřování certifikátů HTTPS. Neměňte, pokud nerozumíte rizikům.",
@@ -53,33 +53,33 @@
"ChmodFolderHelpTextWarning": "Funguje to pouze v případě, že je uživatel souboru Radarr vlastníkem souboru. Je lepší zajistit, aby stahovací klient správně nastavil oprávnění.", "ChmodFolderHelpTextWarning": "Funguje to pouze v případě, že je uživatel souboru Radarr vlastníkem souboru. Je lepší zajistit, aby stahovací klient správně nastavil oprávnění.",
"ChownGroupHelpText": "Název skupiny nebo gid. Použijte gid pro vzdálené systémy souborů.", "ChownGroupHelpText": "Název skupiny nebo gid. Použijte gid pro vzdálené systémy souborů.",
"ChownGroupHelpTextWarning": "Funguje to pouze v případě, že je uživatel souboru Radarr vlastníkem souboru. Je lepší zajistit, aby stahovací klient používal stejnou skupinu jako Radarr.", "ChownGroupHelpTextWarning": "Funguje to pouze v případě, že je uživatel souboru Radarr vlastníkem souboru. Je lepší zajistit, aby stahovací klient používal stejnou skupinu jako Radarr.",
"Clear": "Průhledná", "Clear": "Vyčistit",
"ClickToChangeQuality": "Klepnutím změníte kvalitu", "ClickToChangeQuality": "Kliknutím změníte kvalitu",
"ClientPriority": "Priorita klienta", "ClientPriority": "Priorita klienta",
"CloneIndexer": "Klonovat indexátor", "CloneIndexer": "Klonovat indexátor",
"CloneProfile": "Klonovat profil", "CloneProfile": "Klonovat profil",
"Close": "Zavřít", "Close": "Zavřít",
"Columns": "Sloupce", "Columns": "Sloupce",
"CompletedDownloadHandling": "Zpracování stahování bylo dokončeno", "CompletedDownloadHandling": "Zpracování stahování bylo dokončeno",
"ConnectSettings": "Připojit nastavení", "ConnectSettings": "Nastavení připojení",
"Connections": "Připojení", "Connections": "Připojení",
"CopyUsingHardlinksHelpText": "Hardlinks použijte, když se pokoušíte kopírovat soubory z torrentů, které se stále používají", "CopyUsingHardlinksHelpText": "Hardlinks použijte, když se pokoušíte kopírovat soubory z torrentů, které se stále používají",
"CopyUsingHardlinksHelpTextWarning": "Zámky souborů mohou občas zabránit přejmenování souborů, které se právě vysazují. Výsev můžete dočasně deaktivovat a použít funkci Radarr pro přejmenování.", "CopyUsingHardlinksHelpTextWarning": "Zámky souborů mohou občas zabránit přejmenování souborů, které se právě vysazují. Výsev můžete dočasně deaktivovat a použít funkci Radarr pro přejmenování.",
"CreateEmptyAuthorFoldersHelpText": "Během skenování disku vytvářejte chybějící složky filmů", "CreateEmptyAuthorFoldersHelpText": "Během skenování disku vytvářejte chybějící složky filmů",
"CreateGroup": "Vytvořit skupinu", "CreateGroup": "Vytvořit skupinu",
"CutoffHelpText": "Jakmile je této kvality dosaženo, Radarr již nebude stahovat filmy", "CutoffHelpText": "Jakmile je této kvality dosaženo, Radarr již nebude stahovat filmy",
"CutoffUnmet": "Cut-off Unmet", "CutoffUnmet": "Vynechat nevyhovující",
"DBMigration": "Migrace databáze", "DBMigration": "Migrace databáze",
"Dates": "Termíny", "Dates": "Termíny",
"DelayProfile": "Zpožděný profil", "DelayProfile": "Profil zpoždění",
"DelayProfiles": "Profily zpoždění", "DelayProfiles": "Profily zpoždění",
"DelayingDownloadUntilInterp": "Zpoždění stahování do {0} o {1}", "DelayingDownloadUntilInterp": "Zpoždění stahování do {0} o {1}",
"Delete": "Vymazat", "Delete": "Smazat",
"DeleteBackup": "Odstranit zálohu", "DeleteBackup": "Odstranění zálohy",
"DeleteBackupMessageText": "Opravdu chcete smazat zálohu „{0}“?", "DeleteBackupMessageText": "Opravdu chcete odstranit zálohu '{name}'?",
"DeleteDelayProfile": "Smazat profil zpoždění", "DeleteDelayProfile": "Odstranění profilu zpoždění",
"DeleteDelayProfileMessageText": "Opravdu chcete smazat tento profil zpoždění?", "DeleteDelayProfileMessageText": "Opravdu chcete smazat tento profil zpoždění?",
"DeleteDownloadClient": "Odstranit staženého klienta", "DeleteDownloadClient": "Odstranění klienta pro stahování",
"DeleteDownloadClientMessageText": "Opravdu chcete odstranit klienta pro stahování „{0}“?", "DeleteDownloadClientMessageText": "Opravdu chcete odstranit klienta pro stahování „{0}“?",
"DeleteEmptyFolders": "Odstraňte prázdné složky", "DeleteEmptyFolders": "Odstraňte prázdné složky",
"DeleteEmptyFoldersHelpText": "Během skenování disku a při mazání filmových souborů odstraňte prázdné složky s filmy", "DeleteEmptyFoldersHelpText": "Během skenování disku a při mazání filmových souborů odstraňte prázdné složky s filmy",
@@ -455,12 +455,12 @@
"ShowTitle": "Ukázat nadpis", "ShowTitle": "Ukázat nadpis",
"RemoveFromBlocklist": "Odebrat z černé listiny", "RemoveFromBlocklist": "Odebrat z černé listiny",
"UnableToLoadBlocklist": "Nelze načíst černou listinu", "UnableToLoadBlocklist": "Nelze načíst černou listinu",
"Component": "Součástka", "Component": "Komponenta",
"Level": "Úroveň", "Level": "Úroveň",
"ReleaseBranchCheckOfficialBranchMessage": "Pobočka {0} není platná větev vydání Radarr, nebudete dostávat aktualizace", "ReleaseBranchCheckOfficialBranchMessage": "Pobočka {0} není platná větev vydání Radarr, nebudete dostávat aktualizace",
"Time": "Čas", "Time": "Čas",
"Blocklist": "Černá listina", "Blocklist": "Blocklist",
"BlocklistRelease": "Vydání černé listiny", "BlocklistRelease": "Blocklist pro vydání",
"ShowUnknownAuthorItems": "Zobrazit neznámé položky filmu", "ShowUnknownAuthorItems": "Zobrazit neznámé položky filmu",
"ThisCannotBeCancelled": "Toto nelze zrušit po spuštění bez restartování Radarru.", "ThisCannotBeCancelled": "Toto nelze zrušit po spuštění bez restartování Radarru.",
"UnselectAll": "Odznačit vše", "UnselectAll": "Odznačit vše",
@@ -485,7 +485,7 @@
"SettingsRemotePathMappingRemotePath": "Vzdálená cesta", "SettingsRemotePathMappingRemotePath": "Vzdálená cesta",
"UISettingsSummary": "Možnosti kalendáře, data a barev", "UISettingsSummary": "Možnosti kalendáře, data a barev",
"DownloadClientsSettingsSummary": "Stahování klientů, zpracování stahování a mapování vzdálených cest", "DownloadClientsSettingsSummary": "Stahování klientů, zpracování stahování a mapování vzdálených cest",
"AppDataLocationHealthCheckMessage": "Aktualizace nebude možné zabránit smazání AppData při aktualizaci", "AppDataLocationHealthCheckMessage": "Aktualizace nebude možná, aby se zabránilo odstranění AppData při aktualizaci",
"FileWasDeletedByViaUI": "Soubor byl odstraněn prostřednictvím uživatelského rozhraní", "FileWasDeletedByViaUI": "Soubor byl odstraněn prostřednictvím uživatelského rozhraní",
"IndexersSettingsSummary": "Indexery a omezení vydání", "IndexersSettingsSummary": "Indexery a omezení vydání",
"IndexerStatusCheckSingleClientMessage": "Indexery nedostupné z důvodu selhání: {0}", "IndexerStatusCheckSingleClientMessage": "Indexery nedostupné z důvodu selhání: {0}",
@@ -556,7 +556,7 @@
"Conditions": "Podmínky", "Conditions": "Podmínky",
"CopyToClipboard": "Zkopírovat do schránky", "CopyToClipboard": "Zkopírovat do schránky",
"CustomFormat": "Vlastní formát", "CustomFormat": "Vlastní formát",
"DeleteCustomFormat": "Odstranit vlastní formát", "DeleteCustomFormat": "Odstranění vlastního formátu",
"DeleteCustomFormatMessageText": "Opravdu chcete odstranit indexer „{0}“?", "DeleteCustomFormatMessageText": "Opravdu chcete odstranit indexer „{0}“?",
"ExportCustomFormat": "Exportovat vlastní formát", "ExportCustomFormat": "Exportovat vlastní formát",
"Formats": "Formáty", "Formats": "Formáty",
@@ -573,14 +573,14 @@
"HideAdvanced": "Skrýt pokročilé", "HideAdvanced": "Skrýt pokročilé",
"ShowAdvanced": "Zobrazit pokročilé", "ShowAdvanced": "Zobrazit pokročilé",
"ShownClickToHide": "Zobrazeno, kliknutím se skryjete", "ShownClickToHide": "Zobrazeno, kliknutím se skryjete",
"ColonReplacement": "Výměna tlustého střeva", "ColonReplacement": "Nahrazení dvojtečky",
"ReplaceWithDash": "Nahraďte Dash", "ReplaceWithDash": "Nahraďte Dash",
"ReplaceWithSpaceDashSpace": "Nahraďte Space Dash Space", "ReplaceWithSpaceDashSpace": "Nahraďte Space Dash Space",
"ReplaceWithSpaceDash": "Nahraďte Space Dash", "ReplaceWithSpaceDash": "Nahraďte Space Dash",
"DeleteRemotePathMapping": "Upravit vzdálené mapování cesty", "DeleteRemotePathMapping": "Upravit vzdálené mapování cesty",
"DeleteRemotePathMappingMessageText": "Opravdu chcete toto vzdálené mapování cesty odstranit?", "DeleteRemotePathMappingMessageText": "Opravdu chcete toto vzdálené mapování cesty odstranit?",
"Negated": "Negovaný", "Negated": "Negovaný",
"BlocklistReleases": "Vydání černé listiny", "BlocklistReleases": "Blocklist pro vydání",
"DeleteConditionMessageText": "Opravdu chcete smazat značku „{0}“?", "DeleteConditionMessageText": "Opravdu chcete smazat značku „{0}“?",
"RemoveSelectedItemBlocklistMessageText": "Opravdu chcete odebrat vybrané položky z černé listiny?", "RemoveSelectedItemBlocklistMessageText": "Opravdu chcete odebrat vybrané položky z černé listiny?",
"RemoveSelectedItemQueueMessageText": "Opravdu chcete odebrat {0} položku {1} z fronty?", "RemoveSelectedItemQueueMessageText": "Opravdu chcete odebrat {0} položku {1} z fronty?",
@@ -597,12 +597,12 @@
"NoChange": "Žádná změna", "NoChange": "Žádná změna",
"RemovingTag": "Odebírání značky", "RemovingTag": "Odebírání značky",
"SetTags": "Nastavit značky", "SetTags": "Nastavit značky",
"ApplyTagsHelpTextAdd": "Přidat: Přidejte značky do existujícího seznamu značek", "ApplyTagsHelpTextAdd": "Přidat: Přidá značky k již existujícímu seznamu",
"ApplyTagsHelpTextHowToApplyDownloadClients": "Jak použít značky na vybrané filmy", "ApplyTagsHelpTextHowToApplyDownloadClients": "Jak použít značky na vybrané klienty pro stahování",
"ApplyTagsHelpTextHowToApplyImportLists": "Jak použít značky na vybrané filmy", "ApplyTagsHelpTextHowToApplyImportLists": "Jak použít značky na vybrané importní seznamy",
"ApplyTagsHelpTextHowToApplyIndexers": "Jak použít značky na vybrané filmy", "ApplyTagsHelpTextHowToApplyIndexers": "Jak použít značky na vybrané indexátory",
"ApplyTagsHelpTextRemove": "Odebrat: Odebere zadané značky", "ApplyTagsHelpTextRemove": "Odebrat: Odebrat zadané značky",
"ApplyTagsHelpTextReplace": "Nahradit: Nahradit tagy zadanými tagy (pro vymazání všech tagů zadejte žádné tagy)", "ApplyTagsHelpTextReplace": "Nahradit: Nahradit značky zadanými značkami (zadáním žádné značky vymažete všechny značky)",
"DeleteSelectedDownloadClients": "Odstranit staženého klienta", "DeleteSelectedDownloadClients": "Odstranit staženého klienta",
"DeleteSelectedIndexersMessageText": "Opravdu chcete odstranit indexer „{0}“?", "DeleteSelectedIndexersMessageText": "Opravdu chcete odstranit indexer „{0}“?",
"Yes": "Ano", "Yes": "Ano",
@@ -617,19 +617,51 @@
"Medium": "Střední", "Medium": "Střední",
"NoResultsFound": "Nebyly nalezeny žádné výsledky", "NoResultsFound": "Nebyly nalezeny žádné výsledky",
"SomeResultsAreHiddenByTheAppliedFilter": "Některé výsledky jsou použitým filtrem skryty", "SomeResultsAreHiddenByTheAppliedFilter": "Některé výsledky jsou použitým filtrem skryty",
"AllResultsAreHiddenByTheAppliedFilter": "Všechny výsledky jsou skryty použitým filtrem", "AllResultsAreHiddenByTheAppliedFilter": "Všechny výsledky jsou schovány použitým filtrem",
"Events": "Události", "Events": "Události",
"FreeSpace": "Volný prostor", "FreeSpace": "Volný prostor",
"System": "Systém", "System": "Systém",
"TotalSpace": "Celkový prostor", "TotalSpace": "Celkový prostor",
"ConnectionLost": "Spojení ztraceno", "ConnectionLost": "Spojení ztraceno",
"ConnectionLostReconnect": "Radarr se pokusí připojit automaticky, nebo můžete kliknout na znovu načíst níže.", "ConnectionLostReconnect": "{appName} se pokusí připojit automaticky, nebo můžete kliknout na tlačítko znovunačtení níže.",
"ConnectionLostToBackend": "Radarr ztratil spojení s back-endem a pro obnovení funkčnosti bude nutné jej znovu načíst.", "ConnectionLostToBackend": "{appName} ztratila spojení s backendem a pro obnovení funkčnosti bude třeba ji znovu načíst.",
"Large": "Velký", "Large": "Velký",
"LastDuration": "lastDuration", "LastDuration": "lastDuration",
"Ui": "UI", "Ui": "UI",
"WhatsNew": "Co je nového?", "WhatsNew": "Co je nového?",
"Activity": "Aktivita", "Activity": "Aktivita",
"AddNew": "Přidat nový", "AddNew": "Přidat nové",
"NextExecution": "Další spuštění" "NextExecution": "Další spuštění",
"ClickToChangeReleaseGroup": "Kliknutím změníte skupinu vydání",
"ApplicationURL": "URL aplikace",
"ApplicationUrlHelpText": "Externí adresa URL této aplikace včetně http(s)://, portu a základní adresy URL",
"Continuing": "Pokračující",
"AutomaticUpdatesDisabledDocker": "Automatické aktualizace nejsou při použití aktualizačního mechanismu Docker přímo podporovány. Obraz kontejneru je nutné aktualizovat mimo {appName} nebo použít skript",
"AppUpdated": "{appName} aktualizován",
"ApplyChanges": "Použít změny",
"AutoAdd": "Přidat automaticky",
"AutomaticAdd": "Přidat automaticky",
"ChownGroup": "Skupina chown",
"CloneCondition": "Klonovat podmínku",
"Clone": "Klonovat",
"ApiKeyValidationHealthCheckMessage": "Aktualizujte svůj klíč API tak, aby měl alespoň {0} znaků. Můžete to provést prostřednictvím nastavení nebo konfiguračního souboru",
"ChooseImportMethod": "Vyberte mód importu",
"CatalogNumber": "katalogové číslo",
"Publisher": "Vydavatel",
"StatusEndedContinuing": "Pokračující",
"MetadataProfiles": "profil metadat",
"ReleaseProfiles": "profil vydání",
"MetadataProfile": "profil metadat",
"Label": "Etiketa",
"Library": "Knihovna",
"BypassIfAboveCustomFormatScore": "Obejít, pokud je vyšší než skóre vlastního formátu",
"AppUpdatedVersion": "{appName} byla aktualizována na verzi `{version}`, abyste získali nejnovější změny, musíte znovu načíst {appName}.",
"BypassIfAboveCustomFormatScoreHelpText": "Povolit obcházení, pokud má vydání vyšší skóre, než je nakonfigurované minimální skóre vlastního formátu",
"BypassIfHighestQuality": "Obejít v případě nejvyšší kvality",
"Theme": "Motiv",
"MinimumCustomFormatScoreHelpText": "Minimální skóre vlastního formátu požadované pro obejití zpoždění preferovaného protokolu",
"Series": "Seriál",
"DeleteCondition": "Odstranit podmínku",
"Database": "Databáze",
"CountDownloadClientsSelected": "{count} vybraných klientů ke stahování"
} }

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