mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-18 21:34:28 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4dc3d69a11 |
+2
-1
@@ -3,7 +3,8 @@
|
|||||||
|
|
||||||
# Explicitly set bash scripts to have unix endings
|
# Explicitly set bash scripts to have unix endings
|
||||||
*.sh text eol=lf
|
*.sh text eol=lf
|
||||||
distribution/osx/Readarr text eol=lf
|
distribution/debian/* text eol=lf
|
||||||
|
macOS/Readarr text eol=lf
|
||||||
|
|
||||||
# Custom for Visual Studio
|
# Custom for Visual Studio
|
||||||
*.cs diff=csharp
|
*.cs diff=csharp
|
||||||
|
|||||||
@@ -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 Discord first'
|
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Reddit or Discord first'
|
||||||
labels: ['Type: Bug', 'Status: Needs Triage']
|
labels: ['Type: Bug', 'Status: Needs Triage']
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
|
|||||||
@@ -3,3 +3,6 @@ contact_links:
|
|||||||
- name: Support via Discord
|
- name: Support via Discord
|
||||||
url: https://readarr.com/discord
|
url: https://readarr.com/discord
|
||||||
about: Chat with users and devs on support and setup related topics.
|
about: Chat with users and devs on support and setup related topics.
|
||||||
|
- name: Support via Reddit
|
||||||
|
url: https://reddit.com/r/Readarr
|
||||||
|
about: Discuss and search thru support topics.
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ 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
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ Note that only one type of a given book is supported. If you want both an audiob
|
|||||||
|
|
||||||
[](https://wiki.servarr.com/readarr)
|
[](https://wiki.servarr.com/readarr)
|
||||||
[](https://readarr.com/discord)
|
[](https://readarr.com/discord)
|
||||||
|
[](https://www.reddit.com/r/readarr)
|
||||||
|
|
||||||
Note: GitHub Issues are for Bugs and Feature Requests Only
|
Note: GitHub Issues are for Bugs and Feature Requests Only
|
||||||
|
|
||||||
|
|||||||
+141
-294
@@ -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.12'
|
majorVersion: '0.3.2'
|
||||||
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.417'
|
dotnetVersion: '6.0.408'
|
||||||
nodeVersion: '16.X'
|
nodeVersion: '16.X'
|
||||||
innoVersion: '6.2.0'
|
innoVersion: '6.2.0'
|
||||||
windowsImage: 'windows-2022'
|
windowsImage: 'windows-2022'
|
||||||
@@ -27,10 +27,6 @@ trigger:
|
|||||||
include:
|
include:
|
||||||
- develop
|
- develop
|
||||||
- master
|
- master
|
||||||
paths:
|
|
||||||
exclude:
|
|
||||||
- .github
|
|
||||||
- src/Readarr.Api.*/openapi.json
|
|
||||||
|
|
||||||
pr:
|
pr:
|
||||||
branches:
|
branches:
|
||||||
@@ -38,37 +34,82 @@ pr:
|
|||||||
- develop
|
- develop
|
||||||
paths:
|
paths:
|
||||||
exclude:
|
exclude:
|
||||||
- .github
|
|
||||||
- src/NzbDrone.Core/Localization/Core
|
- src/NzbDrone.Core/Localization/Core
|
||||||
- src/Readarr.Api.*/openapi.json
|
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- stage: Setup
|
|
||||||
displayName: Setup
|
- stage: Build_Backend_Windows
|
||||||
|
displayName: Build Backend
|
||||||
|
dependsOn: []
|
||||||
jobs:
|
jobs:
|
||||||
- job:
|
- job: Backend
|
||||||
displayName: Build Variables
|
strategy:
|
||||||
|
matrix:
|
||||||
|
Windows:
|
||||||
|
osName: 'Windows'
|
||||||
|
imageName: ${{ variables.windowsImage }}
|
||||||
|
enableAnalysis: 'false'
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: ${{ variables.linuxImage }}
|
vmImage: $(imageName)
|
||||||
|
variables:
|
||||||
|
# Disable stylecop here - linting errors get caught by the analyze task
|
||||||
|
EnableAnalyzers: $(enableAnalysis)
|
||||||
steps:
|
steps:
|
||||||
# Set the build name properly. The 'name' property won't recursively expand so hack here:
|
# Set the build name properly. The 'name' property won't recursively expand so hack here:
|
||||||
- bash: echo "##vso[build.updatebuildnumber]$READARRVERSION"
|
- bash: echo "##vso[build.updatebuildnumber]$READARRVERSION"
|
||||||
displayName: Set Build Name
|
displayName: Set Build Name
|
||||||
|
- checkout: self
|
||||||
|
submodules: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: UseDotNet@2
|
||||||
|
displayName: 'Install .net core'
|
||||||
|
inputs:
|
||||||
|
version: $(dotnetVersion)
|
||||||
- bash: |
|
- bash: |
|
||||||
if [[ $BUILD_REASON == "PullRequest" ]]; then
|
SDK_PATH="${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}"
|
||||||
git diff origin/develop...HEAD --name-only | grep -E "^(src/|azure-pipelines.yml)"
|
BUNDLEDVERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
||||||
echo $? > not_backend_update
|
|
||||||
else
|
if ! grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
||||||
echo 0 > not_backend_update
|
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
||||||
fi
|
fi
|
||||||
cat not_backend_update
|
displayName: Extra Platform Support
|
||||||
displayName: Check for Backend File Changes
|
- task: Cache@2
|
||||||
- publish: not_backend_update
|
inputs:
|
||||||
artifact: not_backend_update
|
key: 'nuget | "$(Agent.OS)" | $(Build.SourcesDirectory)/src/Directory.Packages.props'
|
||||||
displayName: Publish update type
|
path: $(nugetCacheFolder)
|
||||||
- stage: Build_Backend
|
displayName: Cache NuGet packages
|
||||||
displayName: Build Backend
|
- bash: ./build.sh --backend --enable-bsd
|
||||||
dependsOn: Setup
|
displayName: Build Readarr Backend
|
||||||
|
env:
|
||||||
|
NUGET_PACKAGES: $(nugetCacheFolder)
|
||||||
|
- powershell: Get-ChildItem _output\net6.0*,_output\*.Update\* -Recurse | Where { $_.Fullname -notlike "*\publish\*" -and $_.attributes -notlike "*directory*" } | Remove-Item
|
||||||
|
displayName: Clean up intermediate output
|
||||||
|
- publish: $(outputFolder)
|
||||||
|
artifact: '$(osName)Backend'
|
||||||
|
displayName: Publish Backend
|
||||||
|
- publish: '$(testsFolder)/net6.0/win-x64/publish'
|
||||||
|
artifact: win-x64-tests
|
||||||
|
displayName: Publish win-x64 Test Package
|
||||||
|
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
|
||||||
|
artifact: linux-x64-tests
|
||||||
|
displayName: Publish linux-x64 Test Package
|
||||||
|
- publish: '$(testsFolder)/net6.0/linux-x86/publish'
|
||||||
|
artifact: linux-x86-tests
|
||||||
|
displayName: Publish linux-x86 Test Package
|
||||||
|
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
|
||||||
|
artifact: linux-musl-x64-tests
|
||||||
|
displayName: Publish linux-musl-x64 Test Package
|
||||||
|
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
|
||||||
|
artifact: freebsd-x64-tests
|
||||||
|
displayName: Publish freebsd-x64 Test Package
|
||||||
|
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
|
||||||
|
artifact: osx-x64-tests
|
||||||
|
displayName: Publish osx-x64 Test Package
|
||||||
|
|
||||||
|
- stage: Build_Backend_Other
|
||||||
|
displayName: Build Backend (Other OS)
|
||||||
|
dependsOn: []
|
||||||
jobs:
|
jobs:
|
||||||
- job: Backend
|
- job: Backend
|
||||||
strategy:
|
strategy:
|
||||||
@@ -81,10 +122,6 @@ stages:
|
|||||||
osName: 'Mac'
|
osName: 'Mac'
|
||||||
imageName: ${{ variables.macImage }}
|
imageName: ${{ variables.macImage }}
|
||||||
enableAnalysis: 'false'
|
enableAnalysis: 'false'
|
||||||
Windows:
|
|
||||||
osName: 'Windows'
|
|
||||||
imageName: ${{ variables.windowsImage }}
|
|
||||||
enableAnalysis: 'false'
|
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
@@ -100,17 +137,22 @@ stages:
|
|||||||
inputs:
|
inputs:
|
||||||
version: $(dotnetVersion)
|
version: $(dotnetVersion)
|
||||||
- bash: |
|
- bash: |
|
||||||
BUNDLEDVERSIONS=${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}/Microsoft.NETCoreSdk.BundledVersions.props
|
SDK_PATH="${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}"
|
||||||
echo $BUNDLEDVERSIONS
|
BUNDLEDVERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
||||||
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
|
||||||
echo "Extra platforms already enabled"
|
if ! grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
||||||
else
|
|
||||||
echo "Enabling extra platform support"
|
|
||||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
||||||
fi
|
fi
|
||||||
displayName: Enable Extra Platform Support
|
displayName: Extra Platform Support
|
||||||
|
- task: Cache@2
|
||||||
|
inputs:
|
||||||
|
key: 'nuget | "$(Agent.OS)" | $(Build.SourcesDirectory)/src/Directory.Packages.props'
|
||||||
|
path: $(nugetCacheFolder)
|
||||||
|
displayName: Cache NuGet packages
|
||||||
- bash: ./build.sh --backend --enable-extra-platforms
|
- bash: ./build.sh --backend --enable-extra-platforms
|
||||||
displayName: Build Readarr Backend
|
displayName: Build Readarr Backend
|
||||||
|
env:
|
||||||
|
NUGET_PACKAGES: $(nugetCacheFolder)
|
||||||
- bash: |
|
- bash: |
|
||||||
find ${OUTPUTFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \;
|
find ${OUTPUTFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \;
|
||||||
find ${OUTPUTFOLDER} -depth -empty -type d -exec rm -r "{}" \;
|
find ${OUTPUTFOLDER} -depth -empty -type d -exec rm -r "{}" \;
|
||||||
@@ -118,38 +160,10 @@ stages:
|
|||||||
find ${TESTSFOLDER} -depth -empty -type d -exec rm -r "{}" \;
|
find ${TESTSFOLDER} -depth -empty -type d -exec rm -r "{}" \;
|
||||||
displayName: Clean up intermediate output
|
displayName: Clean up intermediate output
|
||||||
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
||||||
- publish: $(outputFolder)
|
|
||||||
artifact: '$(osName)Backend'
|
|
||||||
displayName: Publish Backend
|
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
|
||||||
- publish: '$(testsFolder)/net6.0/win-x64/publish'
|
|
||||||
artifact: win-x64-tests
|
|
||||||
displayName: Publish win-x64 Test Package
|
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
|
||||||
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
|
|
||||||
artifact: linux-x64-tests
|
|
||||||
displayName: Publish linux-x64 Test Package
|
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
|
||||||
- publish: '$(testsFolder)/net6.0/linux-x86/publish'
|
|
||||||
artifact: linux-x86-tests
|
|
||||||
displayName: Publish linux-x86 Test Package
|
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
|
||||||
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
|
|
||||||
artifact: linux-musl-x64-tests
|
|
||||||
displayName: Publish linux-musl-x64 Test Package
|
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
|
||||||
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
|
|
||||||
artifact: freebsd-x64-tests
|
|
||||||
displayName: Publish freebsd-x64 Test Package
|
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
|
||||||
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
|
|
||||||
artifact: osx-x64-tests
|
|
||||||
displayName: Publish osx-x64 Test Package
|
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
|
||||||
|
|
||||||
- stage: Build_Frontend
|
- stage: Build_Frontend
|
||||||
displayName: Frontend
|
displayName: Frontend
|
||||||
dependsOn: Setup
|
dependsOn: []
|
||||||
jobs:
|
jobs:
|
||||||
- job: Build
|
- job: Build
|
||||||
strategy:
|
strategy:
|
||||||
@@ -178,6 +192,7 @@ stages:
|
|||||||
key: 'yarn | "$(osName)" | yarn.lock'
|
key: 'yarn | "$(osName)" | yarn.lock'
|
||||||
restoreKeys: |
|
restoreKeys: |
|
||||||
yarn | "$(osName)"
|
yarn | "$(osName)"
|
||||||
|
yarn
|
||||||
path: $(yarnCacheFolder)
|
path: $(yarnCacheFolder)
|
||||||
displayName: Cache Yarn packages
|
displayName: Cache Yarn packages
|
||||||
- bash: ./build.sh --frontend
|
- bash: ./build.sh --frontend
|
||||||
@@ -189,10 +204,10 @@ stages:
|
|||||||
artifact: '$(osName)Frontend'
|
artifact: '$(osName)Frontend'
|
||||||
displayName: Publish Frontend
|
displayName: Publish Frontend
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
|
||||||
- stage: Installer
|
- stage: Installer
|
||||||
dependsOn:
|
dependsOn:
|
||||||
- Build_Backend
|
- Build_Backend_Windows
|
||||||
- Build_Frontend
|
- Build_Frontend
|
||||||
jobs:
|
jobs:
|
||||||
- job: Windows_Installer
|
- job: Windows_Installer
|
||||||
@@ -216,8 +231,8 @@ stages:
|
|||||||
displayName: Fetch Frontend
|
displayName: Fetch Frontend
|
||||||
- bash: |
|
- bash: |
|
||||||
./build.sh --packages --installer
|
./build.sh --packages --installer
|
||||||
cp distribution/windows/setup/output/Readarr.*win-x64.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Readarr.${BUILDNAME}.windows-core-x64-installer.exe
|
cp setup/output/Readarr.*win-x64.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Readarr.${BUILDNAME}.windows-core-x64-installer.exe
|
||||||
cp distribution/windows/setup/output/Readarr.*win-x86.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Readarr.${BUILDNAME}.windows-core-x86-installer.exe
|
cp setup/output/Readarr.*win-x86.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Readarr.${BUILDNAME}.windows-core-x86-installer.exe
|
||||||
displayName: Create Installers
|
displayName: Create Installers
|
||||||
- publish: $(Build.ArtifactStagingDirectory)
|
- publish: $(Build.ArtifactStagingDirectory)
|
||||||
artifact: 'WindowsInstaller'
|
artifact: 'WindowsInstaller'
|
||||||
@@ -225,7 +240,7 @@ stages:
|
|||||||
|
|
||||||
- stage: Packages
|
- stage: Packages
|
||||||
dependsOn:
|
dependsOn:
|
||||||
- Build_Backend
|
- Build_Backend_Windows
|
||||||
- Build_Frontend
|
- Build_Frontend
|
||||||
jobs:
|
jobs:
|
||||||
- job: Other_Packages
|
- job: Other_Packages
|
||||||
@@ -391,29 +406,14 @@ stages:
|
|||||||
SENTRY_AUTH_TOKEN: $(sentryAuthTokenServarr)
|
SENTRY_AUTH_TOKEN: $(sentryAuthTokenServarr)
|
||||||
SENTRY_ORG: $(sentryOrg)
|
SENTRY_ORG: $(sentryOrg)
|
||||||
SENTRY_URL: $(sentryUrl)
|
SENTRY_URL: $(sentryUrl)
|
||||||
|
|
||||||
- stage: Unit_Test
|
- stage: Unit_Test
|
||||||
displayName: Unit Tests
|
displayName: Unit Tests
|
||||||
dependsOn: Build_Backend
|
dependsOn: Build_Backend_Windows
|
||||||
|
condition: succeeded()
|
||||||
jobs:
|
jobs:
|
||||||
- job: Prepare
|
|
||||||
pool:
|
|
||||||
vmImage: ${{ variables.linuxImage }}
|
|
||||||
steps:
|
|
||||||
- checkout: none
|
|
||||||
- task: DownloadPipelineArtifact@2
|
|
||||||
inputs:
|
|
||||||
buildType: 'current'
|
|
||||||
artifactName: 'not_backend_update'
|
|
||||||
targetPath: '.'
|
|
||||||
- bash: echo "##vso[task.setvariable variable=backendNotUpdated;isOutput=true]$(cat not_backend_update)"
|
|
||||||
name: setVar
|
|
||||||
|
|
||||||
- job: Unit
|
- job: Unit
|
||||||
displayName: Unit Native
|
displayName: Unit Native
|
||||||
dependsOn: Prepare
|
|
||||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
|
||||||
workspace:
|
workspace:
|
||||||
clean: all
|
clean: all
|
||||||
|
|
||||||
@@ -479,8 +479,6 @@ stages:
|
|||||||
|
|
||||||
- job: Unit_Docker
|
- job: Unit_Docker
|
||||||
displayName: Unit Docker
|
displayName: Unit Docker
|
||||||
dependsOn: Prepare
|
|
||||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
alpine:
|
alpine:
|
||||||
@@ -494,11 +492,11 @@ stages:
|
|||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: ${{ variables.linuxImage }}
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
container: $[ variables['containerImage'] ]
|
container: $[ variables['containerImage'] ]
|
||||||
|
|
||||||
timeoutInMinutes: 10
|
timeoutInMinutes: 10
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .NET'
|
displayName: 'Install .NET'
|
||||||
@@ -532,14 +530,12 @@ stages:
|
|||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: '$(testName) Unit Tests'
|
testRunTitle: '$(testName) Unit Tests'
|
||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
|
|
||||||
- job: Unit_LinuxCore_Postgres14
|
- job: Unit_LinuxCore_Postgres
|
||||||
displayName: Unit Native LinuxCore with Postgres14 Database
|
displayName: Unit Native LinuxCore with Postgres Database
|
||||||
dependsOn: Prepare
|
|
||||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
|
||||||
variables:
|
variables:
|
||||||
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
||||||
artifactName: linux-x64-tests
|
artifactName: LinuxCoreTests
|
||||||
Readarr__Postgres__Host: 'localhost'
|
Readarr__Postgres__Host: 'localhost'
|
||||||
Readarr__Postgres__Port: '5432'
|
Readarr__Postgres__Port: '5432'
|
||||||
Readarr__Postgres__User: 'readarr'
|
Readarr__Postgres__User: 'readarr'
|
||||||
@@ -549,7 +545,7 @@ stages:
|
|||||||
vmImage: ${{ variables.linuxImage }}
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
timeoutInMinutes: 10
|
timeoutInMinutes: 10
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .net core'
|
displayName: 'Install .net core'
|
||||||
@@ -560,7 +556,7 @@ stages:
|
|||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: $(artifactName)
|
artifactName: 'linux-x64-Tests'
|
||||||
targetPath: $(testsFolder)
|
targetPath: $(testsFolder)
|
||||||
- bash: find ${TESTSFOLDER} -name "Readarr.Test.Dummy" -exec chmod a+x {} \;
|
- bash: find ${TESTSFOLDER} -name "Readarr.Test.Dummy" -exec chmod a+x {} \;
|
||||||
displayName: Make Test Dummy Executable
|
displayName: Make Test Dummy Executable
|
||||||
@@ -583,84 +579,15 @@ stages:
|
|||||||
inputs:
|
inputs:
|
||||||
testResultsFormat: 'NUnit'
|
testResultsFormat: 'NUnit'
|
||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: 'LinuxCore Postgres14 Unit Tests'
|
testRunTitle: 'LinuxCore Postgres Unit Tests'
|
||||||
failTaskOnFailedTests: true
|
|
||||||
|
|
||||||
- job: Unit_LinuxCore_Postgres15
|
|
||||||
displayName: Unit Native LinuxCore with Postgres15 Database
|
|
||||||
dependsOn: Prepare
|
|
||||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
|
||||||
variables:
|
|
||||||
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
|
||||||
artifactName: linux-x64-tests
|
|
||||||
Readarr__Postgres__Host: 'localhost'
|
|
||||||
Readarr__Postgres__Port: '5432'
|
|
||||||
Readarr__Postgres__User: 'readarr'
|
|
||||||
Readarr__Postgres__Password: 'readarr'
|
|
||||||
|
|
||||||
pool:
|
|
||||||
vmImage: ${{ variables.linuxImage }}
|
|
||||||
|
|
||||||
timeoutInMinutes: 10
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- task: UseDotNet@2
|
|
||||||
displayName: 'Install .net core'
|
|
||||||
inputs:
|
|
||||||
version: $(dotnetVersion)
|
|
||||||
- checkout: none
|
|
||||||
- task: DownloadPipelineArtifact@2
|
|
||||||
displayName: Download Test Artifact
|
|
||||||
inputs:
|
|
||||||
buildType: 'current'
|
|
||||||
artifactName: $(artifactName)
|
|
||||||
targetPath: $(testsFolder)
|
|
||||||
- bash: find ${TESTSFOLDER} -name "Readarr.Test.Dummy" -exec chmod a+x {} \;
|
|
||||||
displayName: Make Test Dummy Executable
|
|
||||||
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
|
||||||
- bash: |
|
|
||||||
docker run -d --name=postgres15 \
|
|
||||||
-e POSTGRES_PASSWORD=readarr \
|
|
||||||
-e POSTGRES_USER=readarr \
|
|
||||||
-p 5432:5432/tcp \
|
|
||||||
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
|
|
||||||
postgres:15
|
|
||||||
displayName: Start postgres
|
|
||||||
- bash: |
|
|
||||||
chmod a+x ${TESTSFOLDER}/test.sh
|
|
||||||
ls -lR ${TESTSFOLDER}
|
|
||||||
${TESTSFOLDER}/test.sh Linux Unit Test
|
|
||||||
displayName: Run Tests
|
|
||||||
- task: PublishTestResults@2
|
|
||||||
displayName: Publish Test Results
|
|
||||||
inputs:
|
|
||||||
testResultsFormat: 'NUnit'
|
|
||||||
testResultsFiles: '**/TestResult.xml'
|
|
||||||
testRunTitle: 'LinuxCore Postgres15 Unit Tests'
|
|
||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
|
|
||||||
- stage: Integration
|
- stage: Integration
|
||||||
displayName: Integration
|
displayName: Integration
|
||||||
dependsOn: Packages
|
dependsOn: Packages
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
- job: Prepare
|
|
||||||
pool:
|
|
||||||
vmImage: ${{ variables.linuxImage }}
|
|
||||||
steps:
|
|
||||||
- checkout: none
|
|
||||||
- task: DownloadPipelineArtifact@2
|
|
||||||
inputs:
|
|
||||||
buildType: 'current'
|
|
||||||
artifactName: 'not_backend_update'
|
|
||||||
targetPath: '.'
|
|
||||||
- bash: echo "##vso[task.setvariable variable=backendNotUpdated;isOutput=true]$(cat not_backend_update)"
|
|
||||||
name: setVar
|
|
||||||
|
|
||||||
- job: Integration_Native
|
- job: Integration_Native
|
||||||
displayName: Integration Native
|
displayName: Integration Native
|
||||||
dependsOn: Prepare
|
|
||||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
MacCore:
|
MacCore:
|
||||||
@@ -681,7 +608,7 @@ stages:
|
|||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .net core'
|
displayName: 'Install .net core'
|
||||||
@@ -703,7 +630,7 @@ stages:
|
|||||||
targetPath: $(Build.ArtifactStagingDirectory)
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
- task: ExtractFiles@1
|
- task: ExtractFiles@1
|
||||||
inputs:
|
inputs:
|
||||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||||
displayName: Extract Package
|
displayName: Extract Package
|
||||||
- bash: |
|
- bash: |
|
||||||
@@ -722,10 +649,8 @@ stages:
|
|||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
displayName: Publish Test Results
|
displayName: Publish Test Results
|
||||||
|
|
||||||
- job: Integration_LinuxCore_Postgres14
|
- job: Integration_LinuxCore_Postgres
|
||||||
displayName: Integration Native LinuxCore with Postgres14 Database
|
displayName: Integration Native LinuxCore with Postgres Database
|
||||||
dependsOn: Prepare
|
|
||||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
|
||||||
variables:
|
variables:
|
||||||
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
||||||
Readarr__Postgres__Host: 'localhost'
|
Readarr__Postgres__Host: 'localhost'
|
||||||
@@ -757,7 +682,7 @@ stages:
|
|||||||
targetPath: $(Build.ArtifactStagingDirectory)
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
- task: ExtractFiles@1
|
- task: ExtractFiles@1
|
||||||
inputs:
|
inputs:
|
||||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||||
displayName: Extract Package
|
displayName: Extract Package
|
||||||
- bash: |
|
- bash: |
|
||||||
@@ -780,77 +705,12 @@ stages:
|
|||||||
inputs:
|
inputs:
|
||||||
testResultsFormat: 'NUnit'
|
testResultsFormat: 'NUnit'
|
||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: 'Integration LinuxCore Postgres14 Database Integration Tests'
|
testRunTitle: 'Integration LinuxCore Postgres Database Integration Tests'
|
||||||
failTaskOnFailedTests: true
|
|
||||||
displayName: Publish Test Results
|
|
||||||
|
|
||||||
|
|
||||||
- job: Integration_LinuxCore_Postgres15
|
|
||||||
displayName: Integration Native LinuxCore with Postgres Database
|
|
||||||
dependsOn: Prepare
|
|
||||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
|
||||||
variables:
|
|
||||||
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
|
||||||
Readarr__Postgres__Host: 'localhost'
|
|
||||||
Readarr__Postgres__Port: '5432'
|
|
||||||
Readarr__Postgres__User: 'readarr'
|
|
||||||
Readarr__Postgres__Password: 'readarr'
|
|
||||||
|
|
||||||
pool:
|
|
||||||
vmImage: ${{ variables.linuxImage }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- task: UseDotNet@2
|
|
||||||
displayName: 'Install .net core'
|
|
||||||
inputs:
|
|
||||||
version: $(dotnetVersion)
|
|
||||||
- checkout: none
|
|
||||||
- task: DownloadPipelineArtifact@2
|
|
||||||
displayName: Download Test Artifact
|
|
||||||
inputs:
|
|
||||||
buildType: 'current'
|
|
||||||
artifactName: 'linux-x64-tests'
|
|
||||||
targetPath: $(testsFolder)
|
|
||||||
- task: DownloadPipelineArtifact@2
|
|
||||||
displayName: Download Build Artifact
|
|
||||||
inputs:
|
|
||||||
buildType: 'current'
|
|
||||||
artifactName: Packages
|
|
||||||
itemPattern: '**/$(pattern)'
|
|
||||||
targetPath: $(Build.ArtifactStagingDirectory)
|
|
||||||
- task: ExtractFiles@1
|
|
||||||
inputs:
|
|
||||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
|
||||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
|
||||||
displayName: Extract Package
|
|
||||||
- bash: |
|
|
||||||
mkdir -p ./bin/
|
|
||||||
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Readarr/. ./bin/
|
|
||||||
displayName: Move Package Contents
|
|
||||||
- bash: |
|
|
||||||
docker run -d --name=postgres15 \
|
|
||||||
-e POSTGRES_PASSWORD=readarr \
|
|
||||||
-e POSTGRES_USER=readarr \
|
|
||||||
-p 5432:5432/tcp \
|
|
||||||
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
|
|
||||||
postgres:15
|
|
||||||
displayName: Start postgres
|
|
||||||
- bash: |
|
|
||||||
chmod a+x ${TESTSFOLDER}/test.sh
|
|
||||||
${TESTSFOLDER}/test.sh Linux Integration Test
|
|
||||||
displayName: Run Integration Tests
|
|
||||||
- task: PublishTestResults@2
|
|
||||||
inputs:
|
|
||||||
testResultsFormat: 'NUnit'
|
|
||||||
testResultsFiles: '**/TestResult.xml'
|
|
||||||
testRunTitle: 'Integration LinuxCore Postgres15 Database Integration Tests'
|
|
||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
displayName: Publish Test Results
|
displayName: Publish Test Results
|
||||||
|
|
||||||
- job: Integration_FreeBSD
|
- job: Integration_FreeBSD
|
||||||
displayName: Integration Native FreeBSD
|
displayName: Integration Native FreeBSD
|
||||||
dependsOn: Prepare
|
|
||||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
|
||||||
workspace:
|
workspace:
|
||||||
clean: all
|
clean: all
|
||||||
variables:
|
variables:
|
||||||
@@ -895,8 +755,6 @@ stages:
|
|||||||
|
|
||||||
- job: Integration_Docker
|
- job: Integration_Docker
|
||||||
displayName: Integration Docker
|
displayName: Integration Docker
|
||||||
dependsOn: Prepare
|
|
||||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
alpine:
|
alpine:
|
||||||
@@ -915,7 +773,7 @@ stages:
|
|||||||
container: $[ variables['containerImage'] ]
|
container: $[ variables['containerImage'] ]
|
||||||
|
|
||||||
timeoutInMinutes: 15
|
timeoutInMinutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .NET'
|
displayName: 'Install .NET'
|
||||||
@@ -943,7 +801,7 @@ stages:
|
|||||||
targetPath: $(Build.ArtifactStagingDirectory)
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
- task: ExtractFiles@1
|
- task: ExtractFiles@1
|
||||||
inputs:
|
inputs:
|
||||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||||
displayName: Extract Package
|
displayName: Extract Package
|
||||||
- bash: |
|
- bash: |
|
||||||
@@ -965,7 +823,7 @@ stages:
|
|||||||
- stage: Automation
|
- stage: Automation
|
||||||
displayName: Automation
|
displayName: Automation
|
||||||
dependsOn: Packages
|
dependsOn: Packages
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
- job: Automation
|
- job: Automation
|
||||||
strategy:
|
strategy:
|
||||||
@@ -975,23 +833,20 @@ stages:
|
|||||||
artifactName: 'linux-x64'
|
artifactName: 'linux-x64'
|
||||||
imageName: ${{ variables.linuxImage }}
|
imageName: ${{ variables.linuxImage }}
|
||||||
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
||||||
failBuild: true
|
|
||||||
Mac:
|
Mac:
|
||||||
osName: 'Mac'
|
osName: 'Mac'
|
||||||
artifactName: 'osx-x64'
|
artifactName: 'osx-x64'
|
||||||
imageName: ${{ variables.macImage }}
|
imageName: ${{ variables.macImage }}
|
||||||
pattern: 'Readarr.*.osx-core-x64.tar.gz'
|
pattern: 'Readarr.*.osx-core-x64.tar.gz'
|
||||||
failBuild: true
|
|
||||||
Windows:
|
Windows:
|
||||||
osName: 'Windows'
|
osName: 'Windows'
|
||||||
artifactName: 'win-x64'
|
artifactName: 'win-x64'
|
||||||
imageName: ${{ variables.windowsImage }}
|
imageName: ${{ variables.windowsImage }}
|
||||||
pattern: 'Readarr.*.windows-core-x64.zip'
|
pattern: 'Readarr.*.windows-core-x64.zip'
|
||||||
failBuild: true
|
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .net core'
|
displayName: 'Install .net core'
|
||||||
@@ -1013,7 +868,7 @@ stages:
|
|||||||
targetPath: $(Build.ArtifactStagingDirectory)
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
- task: ExtractFiles@1
|
- task: ExtractFiles@1
|
||||||
inputs:
|
inputs:
|
||||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||||
displayName: Extract Package
|
displayName: Extract Package
|
||||||
- bash: |
|
- bash: |
|
||||||
@@ -1033,35 +888,20 @@ stages:
|
|||||||
TargetFolder: '$(Build.ArtifactStagingDirectory)/screenshots'
|
TargetFolder: '$(Build.ArtifactStagingDirectory)/screenshots'
|
||||||
- publish: $(Build.ArtifactStagingDirectory)/screenshots
|
- publish: $(Build.ArtifactStagingDirectory)/screenshots
|
||||||
artifact: '$(osName)AutomationScreenshots'
|
artifact: '$(osName)AutomationScreenshots'
|
||||||
displayName: Publish Screenshot Bundle
|
|
||||||
condition: and(succeeded(), eq(variables['System.JobAttempt'], '1'))
|
condition: and(succeeded(), eq(variables['System.JobAttempt'], '1'))
|
||||||
|
displayName: Publish Screenshot Bundle
|
||||||
- task: PublishTestResults@2
|
- task: PublishTestResults@2
|
||||||
inputs:
|
inputs:
|
||||||
testResultsFormat: 'NUnit'
|
testResultsFormat: 'NUnit'
|
||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: '$(osName) Automation Tests'
|
testRunTitle: '$(osName) Automation Tests'
|
||||||
failTaskOnFailedTests: $(failBuild)
|
failTaskOnFailedTests: true
|
||||||
displayName: Publish Test Results
|
displayName: Publish Test Results
|
||||||
|
|
||||||
- stage: Analyze
|
- stage: Analyze
|
||||||
dependsOn:
|
dependsOn: []
|
||||||
- Setup
|
|
||||||
displayName: Analyze
|
displayName: Analyze
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
- job: Prepare
|
|
||||||
pool:
|
|
||||||
vmImage: ${{ variables.linuxImage }}
|
|
||||||
steps:
|
|
||||||
- checkout: none
|
|
||||||
- task: DownloadPipelineArtifact@2
|
|
||||||
inputs:
|
|
||||||
buildType: 'current'
|
|
||||||
artifactName: 'not_backend_update'
|
|
||||||
targetPath: '.'
|
|
||||||
- bash: echo "##vso[task.setvariable variable=backendNotUpdated;isOutput=true]$(cat not_backend_update)"
|
|
||||||
name: setVar
|
|
||||||
|
|
||||||
- job: Lint_Frontend
|
- job: Lint_Frontend
|
||||||
displayName: Lint Frontend
|
displayName: Lint Frontend
|
||||||
strategy:
|
strategy:
|
||||||
@@ -1087,6 +927,7 @@ stages:
|
|||||||
key: 'yarn | "$(osName)" | yarn.lock'
|
key: 'yarn | "$(osName)" | yarn.lock'
|
||||||
restoreKeys: |
|
restoreKeys: |
|
||||||
yarn | "$(osName)"
|
yarn | "$(osName)"
|
||||||
|
yarn
|
||||||
path: $(yarnCacheFolder)
|
path: $(yarnCacheFolder)
|
||||||
displayName: Cache Yarn packages
|
displayName: Cache Yarn packages
|
||||||
- bash: ./build.sh --lint
|
- bash: ./build.sh --lint
|
||||||
@@ -1115,16 +956,11 @@ stages:
|
|||||||
cliProjectVersion: '$(readarrVersion)'
|
cliProjectVersion: '$(readarrVersion)'
|
||||||
cliSources: './frontend'
|
cliSources: './frontend'
|
||||||
- task: SonarCloudAnalyze@1
|
- task: SonarCloudAnalyze@1
|
||||||
|
|
||||||
- job: Api_Docs
|
- job: Api_Docs
|
||||||
displayName: API Docs
|
displayName: API Docs
|
||||||
dependsOn: Prepare
|
|
||||||
condition: |
|
condition: |
|
||||||
and
|
and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
|
||||||
(
|
|
||||||
and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')),
|
|
||||||
and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
|
||||||
)
|
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: ${{ variables.windowsImage }}
|
vmImage: ${{ variables.windowsImage }}
|
||||||
@@ -1137,7 +973,7 @@ stages:
|
|||||||
- checkout: self
|
- checkout: self
|
||||||
submodules: true
|
submodules: true
|
||||||
persistCredentials: true
|
persistCredentials: true
|
||||||
fetchDepth: 1
|
fetchDepth: 1
|
||||||
- bash: ./docs.sh Windows
|
- bash: ./docs.sh Windows
|
||||||
displayName: Create openapi.json
|
displayName: Create openapi.json
|
||||||
- bash: |
|
- bash: |
|
||||||
@@ -1145,9 +981,10 @@ stages:
|
|||||||
git config --global user.name "Servarr"
|
git config --global user.name "Servarr"
|
||||||
git checkout -b api-docs
|
git checkout -b api-docs
|
||||||
git add .
|
git add .
|
||||||
if git status | grep -q modified
|
git status
|
||||||
|
if git status | grep modified
|
||||||
then
|
then
|
||||||
git commit -am 'Automated API Docs update'
|
git commit -am 'Automated API Docs update [skip ci]'
|
||||||
git push -f --set-upstream origin api-docs
|
git push -f --set-upstream origin api-docs
|
||||||
curl -X POST -H "Authorization: token ${GITHUBTOKEN}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/readarr/readarr/pulls -d '{"head":"api-docs","base":"develop","title":"Update API docs"}'
|
curl -X POST -H "Authorization: token ${GITHUBTOKEN}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/readarr/readarr/pulls -d '{"head":"api-docs","base":"develop","title":"Update API docs"}'
|
||||||
else
|
else
|
||||||
@@ -1171,25 +1008,33 @@ stages:
|
|||||||
|
|
||||||
- job: Analyze_Backend
|
- job: Analyze_Backend
|
||||||
displayName: Backend
|
displayName: Backend
|
||||||
dependsOn: Prepare
|
|
||||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
disable.coverage.autogenerate: 'true'
|
disable.coverage.autogenerate: 'true'
|
||||||
EnableAnalyzers: 'false'
|
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: ${{ variables.windowsImage }}
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .net core'
|
displayName: 'Install .net core 2.1'
|
||||||
|
inputs:
|
||||||
|
version: 2.1.815
|
||||||
|
- task: UseDotNet@2
|
||||||
|
displayName: 'Install .net core 3.1'
|
||||||
|
inputs:
|
||||||
|
version: 3.1.413
|
||||||
|
- task: UseDotNet@2
|
||||||
|
displayName: 'Install .net core 5.0'
|
||||||
inputs:
|
inputs:
|
||||||
version: $(dotnetVersion)
|
version: $(dotnetVersion)
|
||||||
- checkout: self # Need history for Sonar analysis
|
- checkout: self # Need history for Sonar analysis
|
||||||
submodules: true
|
submodules: true
|
||||||
- powershell: Set-Service SCardSvr -StartupType Manual
|
- task: Cache@2
|
||||||
displayName: Enable Windows Test Service
|
inputs:
|
||||||
|
key: 'nuget | "$(Agent.OS)" | $(Build.SourcesDirectory)/src/Directory.Packages.props'
|
||||||
|
path: $(nugetCacheFolder)
|
||||||
|
displayName: Cache NuGet packages
|
||||||
|
|
||||||
- task: SonarCloudPrepare@1
|
- task: SonarCloudPrepare@1
|
||||||
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
||||||
inputs:
|
inputs:
|
||||||
@@ -1200,14 +1045,16 @@ stages:
|
|||||||
projectName: 'Readarr'
|
projectName: 'Readarr'
|
||||||
projectVersion: '$(readarrVersion)'
|
projectVersion: '$(readarrVersion)'
|
||||||
extraProperties: |
|
extraProperties: |
|
||||||
sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,**/ExternalModules/**,./src/Libraries/**
|
sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,./src/Libraries/**
|
||||||
sonar.coverage.exclusions=**/Readarr.Api.V1/**/*
|
sonar.coverage.exclusions=**/Readarr.Api.V1/**/*
|
||||||
sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
|
sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
|
||||||
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
|
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
|
||||||
- bash: |
|
- bash: |
|
||||||
./build.sh --backend -f net6.0 -r win-x64
|
./build.sh --backend -f net6.0 -r linux-x64
|
||||||
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage
|
TEST_DIR=_tests/net6.0/linux-x64/publish/ ./test.sh Linux Unit Coverage
|
||||||
displayName: Coverage Unit Tests
|
displayName: Coverage Unit Tests
|
||||||
|
env:
|
||||||
|
NUGET_PACKAGES: $(nugetCacheFolder)
|
||||||
- task: SonarCloudAnalyze@1
|
- task: SonarCloudAnalyze@1
|
||||||
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
||||||
displayName: Publish SonarCloud Results
|
displayName: Publish SonarCloud Results
|
||||||
@@ -1230,6 +1077,7 @@ stages:
|
|||||||
- Unit_Test
|
- Unit_Test
|
||||||
- Integration
|
- Integration
|
||||||
- Automation
|
- Automation
|
||||||
|
- Build_Backend_Other
|
||||||
condition: eq(variables['system.pullrequest.isfork'], false)
|
condition: eq(variables['system.pullrequest.isfork'], false)
|
||||||
displayName: Build Status Report
|
displayName: Build Status Report
|
||||||
jobs:
|
jobs:
|
||||||
@@ -1253,4 +1101,3 @@ stages:
|
|||||||
DISCORDCHANNELID: $(discordChannelId)
|
DISCORDCHANNELID: $(discordChannelId)
|
||||||
DISCORDWEBHOOKKEY: $(discordWebhookKey)
|
DISCORDWEBHOOKKEY: $(discordWebhookKey)
|
||||||
DISCORDTHREADID: $(discordThreadId)
|
DISCORDTHREADID: $(discordThreadId)
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ UpdateVersionNumber()
|
|||||||
echo "Updating Version Info"
|
echo "Updating Version Info"
|
||||||
sed -i'' -e "s/<AssemblyVersion>[0-9.*]\+<\/AssemblyVersion>/<AssemblyVersion>$READARRVERSION<\/AssemblyVersion>/g" src/Directory.Build.props
|
sed -i'' -e "s/<AssemblyVersion>[0-9.*]\+<\/AssemblyVersion>/<AssemblyVersion>$READARRVERSION<\/AssemblyVersion>/g" src/Directory.Build.props
|
||||||
sed -i'' -e "s/<AssemblyConfiguration>[\$()A-Za-z-]\+<\/AssemblyConfiguration>/<AssemblyConfiguration>${BUILD_SOURCEBRANCHNAME}<\/AssemblyConfiguration>/g" src/Directory.Build.props
|
sed -i'' -e "s/<AssemblyConfiguration>[\$()A-Za-z-]\+<\/AssemblyConfiguration>/<AssemblyConfiguration>${BUILD_SOURCEBRANCHNAME}<\/AssemblyConfiguration>/g" src/Directory.Build.props
|
||||||
sed -i'' -e "s/<string>10.0.0.0<\/string>/<string>$READARRVERSION<\/string>/g" distribution/osx/Readarr.app/Contents/Info.plist
|
sed -i'' -e "s/<string>10.0.0.0<\/string>/<string>$READARRVERSION<\/string>/g" macOS/Readarr.app/Contents/Info.plist
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ PackageMacOSApp()
|
|||||||
|
|
||||||
rm -rf $folder
|
rm -rf $folder
|
||||||
mkdir -p $folder
|
mkdir -p $folder
|
||||||
cp -r distribution/osx/Readarr.app $folder
|
cp -r macOS/Readarr.app $folder
|
||||||
mkdir -p $folder/Readarr.app/Contents/MacOS
|
mkdir -p $folder/Readarr.app/Contents/MacOS
|
||||||
|
|
||||||
echo "Copying Binaries"
|
echo "Copying Binaries"
|
||||||
@@ -245,7 +245,7 @@ BuildInstaller()
|
|||||||
local framework="$1"
|
local framework="$1"
|
||||||
local runtime="$2"
|
local runtime="$2"
|
||||||
|
|
||||||
./_inno/ISCC.exe distribution/windows/setup/readarr.iss "//DFramework=$framework" "//DRuntime=$runtime"
|
./_inno/ISCC.exe setup/readarr.iss "//DFramework=$framework" "//DRuntime=$runtime"
|
||||||
}
|
}
|
||||||
|
|
||||||
InstallInno()
|
InstallInno()
|
||||||
|
|||||||
@@ -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-transform-optional-chaining', { loose }],
|
['@babel/plugin-proposal-optional-chaining', { loose }],
|
||||||
['@babel/plugin-transform-nullish-coalescing-operator', { loose }],
|
['@babel/plugin-proposal-nullish-coalescing-operator', { loose }],
|
||||||
|
|
||||||
// Stage 2
|
// Stage 2
|
||||||
'@babel/plugin-transform-export-namespace-from',
|
'@babel/plugin-proposal-export-namespace-from',
|
||||||
|
|
||||||
// Stage 3
|
// Stage 3
|
||||||
['@babel/plugin-transform-class-properties', { loose }],
|
['@babel/plugin-proposal-class-properties', { loose }],
|
||||||
'@babel/plugin-syntax-dynamic-import'
|
'@babel/plugin-syntax-dynamic-import'
|
||||||
],
|
],
|
||||||
env: {
|
env: {
|
||||||
|
|||||||
@@ -338,8 +338,4 @@ Queue.propTypes = {
|
|||||||
onRemoveSelectedPress: PropTypes.func.isRequired
|
onRemoveSelectedPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
Queue.defaultProps = {
|
|
||||||
count: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Queue;
|
export default Queue;
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ class RemoveQueueItemModal extends Component {
|
|||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="remove"
|
name="remove"
|
||||||
value={remove}
|
value={remove}
|
||||||
helpTextWarning={translate('RemoveHelpTextWarning')}
|
helpTextWarning={translate('RemoveFromDownloadClientHelpTextWarning')}
|
||||||
isDisabled={!canIgnore}
|
isDisabled={!canIgnore}
|
||||||
onChange={this.onRemoveChange}
|
onChange={this.onRemoveChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
.version {
|
.version {
|
||||||
margin: 0 3px;
|
margin: 0 3px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-family: var(--defaultFontFamily);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.maintenance {
|
.maintenance {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
|
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
import ModalContent from 'Components/Modal/ModalContent';
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
@@ -65,12 +64,12 @@ function AppUpdatedModalContent(props) {
|
|||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
{translate('AppUpdated', { appName: 'Readarr' })}
|
Readarr Updated
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<div>
|
<div>
|
||||||
<InlineMarkdown data={translate('AppUpdatedVersion', { appName: 'Readarr', version })} blockClassName={styles.version} />
|
Version <span className={styles.version}>{version}</span> of Readarr has been installed, in order to get the latest changes you'll need to reload Readarr.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -78,14 +77,16 @@ function AppUpdatedModalContent(props) {
|
|||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
!update.changes &&
|
!update.changes &&
|
||||||
<div className={styles.maintenance}>{translate('MaintenanceRelease')}</div>
|
<div className={styles.maintenance}>
|
||||||
|
{translate('MaintenanceRelease')}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!!update.changes &&
|
!!update.changes &&
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.changes}>
|
<div className={styles.changes}>
|
||||||
{translate('WhatsNew')}
|
What's new?
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UpdateChanges
|
<UpdateChanges
|
||||||
@@ -112,14 +113,14 @@ function AppUpdatedModalContent(props) {
|
|||||||
<Button
|
<Button
|
||||||
onPress={onSeeChangesPress}
|
onPress={onSeeChangesPress}
|
||||||
>
|
>
|
||||||
{translate('RecentChanges')}
|
Recent Changes
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
kind={kinds.PRIMARY}
|
kind={kinds.PRIMARY}
|
||||||
onPress={onModalClose}
|
onPress={onModalClose}
|
||||||
>
|
>
|
||||||
{translate('Reload')}
|
Reload
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import ModalContent from 'Components/Modal/ModalContent';
|
|||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import styles from './ConnectionLostModal.css';
|
import styles from './ConnectionLostModal.css';
|
||||||
|
|
||||||
function ConnectionLostModal(props) {
|
function ConnectionLostModal(props) {
|
||||||
@@ -23,16 +22,16 @@ function ConnectionLostModal(props) {
|
|||||||
>
|
>
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
{translate('ConnectionLost')}
|
Connection Lost
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<div>
|
<div>
|
||||||
{translate('ConnectionLostToBackend', { appName: 'Readarr' })}
|
Readarr has lost its connection to the backend and will need to be reloaded to restore functionality.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.automatic}>
|
<div className={styles.automatic}>
|
||||||
{translate('ConnectionLostReconnect', { appName: 'Readarr' })}
|
Readarr will try to connect automatically, or you can click reload below.
|
||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
@@ -40,7 +39,7 @@ function ConnectionLostModal(props) {
|
|||||||
kind={kinds.PRIMARY}
|
kind={kinds.PRIMARY}
|
||||||
onPress={onModalClose}
|
onPress={onModalClose}
|
||||||
>
|
>
|
||||||
{translate('Reload')}
|
Reload
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|||||||
@@ -7,10 +7,13 @@ function findImage(images, coverType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getUrl(image, coverType, size) {
|
function getUrl(image, coverType, size) {
|
||||||
const imageUrl = image?.url;
|
if (image) {
|
||||||
|
// Remove protocol
|
||||||
|
let url = image.url;
|
||||||
|
|
||||||
if (imageUrl) {
|
url = url.replace(`${coverType}.jpg`, `${coverType}-${size}.jpg`);
|
||||||
return imageUrl.replace(`${coverType}.jpg`, `${coverType}-${size}.jpg`);
|
|
||||||
|
return url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,12 @@ const defaultFontSize = parseInt(fonts.defaultFontSize);
|
|||||||
const lineHeight = parseFloat(fonts.lineHeight);
|
const lineHeight = parseFloat(fonts.lineHeight);
|
||||||
|
|
||||||
function getFanartUrl(images) {
|
function getFanartUrl(images) {
|
||||||
return images.find((x) => x.coverType === 'fanart')?.url;
|
const fanartImage = images.find((x) => x.coverType === 'fanart');
|
||||||
|
|
||||||
|
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.BULK_REFRESH_AUTHOR),
|
createCommandExecutingSelector(commandNames.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,7 +21,12 @@ const defaultFontSize = parseInt(fonts.defaultFontSize);
|
|||||||
const lineHeight = parseFloat(fonts.lineHeight);
|
const lineHeight = parseFloat(fonts.lineHeight);
|
||||||
|
|
||||||
function getFanartUrl(images) {
|
function getFanartUrl(images) {
|
||||||
return images.find((x) => x.coverType === 'fanart')?.url;
|
const fanartImage = images.find((x) => x.coverType === 'fanart');
|
||||||
|
|
||||||
|
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.BULK_REFRESH_AUTHOR),
|
createCommandExecutingSelector(commandNames.REFRESH_AUTHOR),
|
||||||
createCommandExecutingSelector(commandNames.BULK_REFRESH_BOOK),
|
createCommandExecutingSelector(commandNames.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),
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ class BookshelfFooter extends Component {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.label}>
|
<div className={styles.label}>
|
||||||
{translate('CountAuthorsSelected', { selectedCount })}
|
{selectedCount} Author(s) Selected
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SpinnerButton
|
<SpinnerButton
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class CalendarConnector extends Component {
|
|||||||
gotoCalendarToday
|
gotoCalendarToday
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
registerPagePopulator(this.repopulate, ['bookFileUpdated', 'bookFileDeleted']);
|
registerPagePopulator(this.repopulate);
|
||||||
|
|
||||||
if (useCurrentPage) {
|
if (useCurrentPage) {
|
||||||
fetchCalendar();
|
fetchCalendar();
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
.description {
|
||||||
|
line-height: $lineHeight;
|
||||||
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
line-height: $lineHeight;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
|
|||||||
@@ -25,10 +25,6 @@
|
|||||||
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,7 +6,6 @@ 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;
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
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;
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
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,10 +9,6 @@
|
|||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--inputHoverBackgroundColor);
|
background-color: var(--inputHoverBackgroundColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.isDisabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.optionCheck {
|
.optionCheck {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class NumberInput extends Component {
|
|||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
const { value } = this.props;
|
const { value } = this.props;
|
||||||
|
|
||||||
if (!isNaN(value) && value !== prevProps.value && !this.state.isFocused) {
|
if (value !== prevProps.value && !this.state.isFocused) {
|
||||||
this.setState({
|
this.setState({
|
||||||
value: value == null ? '' : value.toString()
|
value: value == null ? '' : value.toString()
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ class SpinnerErrorButton extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
kind,
|
|
||||||
isSpinning,
|
isSpinning,
|
||||||
error,
|
error,
|
||||||
children,
|
children,
|
||||||
@@ -113,7 +112,7 @@ class SpinnerErrorButton extends Component {
|
|||||||
const showIcon = wasSuccessful || hasWarning || hasError;
|
const showIcon = wasSuccessful || hasWarning || hasError;
|
||||||
|
|
||||||
let iconName = icons.CHECK;
|
let iconName = icons.CHECK;
|
||||||
let iconKind = kind === kinds.PRIMARY ? kinds.DEFAULT : kinds.SUCCESS;
|
let iconKind = kinds.SUCCESS;
|
||||||
|
|
||||||
if (hasWarning) {
|
if (hasWarning) {
|
||||||
iconName = icons.WARNING;
|
iconName = icons.WARNING;
|
||||||
@@ -127,7 +126,6 @@ class SpinnerErrorButton extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SpinnerButton
|
<SpinnerButton
|
||||||
kind={kind}
|
|
||||||
isSpinning={isSpinning}
|
isSpinning={isSpinning}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
@@ -156,7 +154,6 @@ class SpinnerErrorButton extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SpinnerErrorButton.propTypes = {
|
SpinnerErrorButton.propTypes = {
|
||||||
kind: PropTypes.oneOf(kinds.all),
|
|
||||||
isSpinning: PropTypes.bool.isRequired,
|
isSpinning: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
children: PropTypes.node.isRequired
|
children: PropTypes.node.isRequired
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ class InlineMarkdown extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
data,
|
data
|
||||||
blockClassName
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
// For now only replace links or code blocks (not both)
|
// For now only replace links or code blocks (not both)
|
||||||
@@ -48,7 +47,7 @@ class InlineMarkdown extends Component {
|
|||||||
markdownBlocks.push(data.substr(endIndex, match.index - endIndex));
|
markdownBlocks.push(data.substr(endIndex, match.index - endIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
markdownBlocks.push(<code key={`code-${match.index}`} className={blockClassName ?? null}>{match[0].substring(1, match[0].length - 1)}</code>);
|
markdownBlocks.push(<code key={`code-${match.index}`}>{match[0].substring(1, match[0].length - 1)}</code>);
|
||||||
endIndex = match.index + match[0].length;
|
endIndex = match.index + match[0].length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,8 +66,7 @@ class InlineMarkdown extends Component {
|
|||||||
|
|
||||||
InlineMarkdown.propTypes = {
|
InlineMarkdown.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
data: PropTypes.string,
|
data: PropTypes.string
|
||||||
blockClassName: PropTypes.string
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default InlineMarkdown;
|
export default InlineMarkdown;
|
||||||
|
|||||||
@@ -202,8 +202,6 @@ class SignalRConnector extends Component {
|
|||||||
this.props.dispatchUpdateItem({ section, ...body.resource });
|
this.props.dispatchUpdateItem({ section, ...body.resource });
|
||||||
} else if (body.action === 'deleted') {
|
} else if (body.action === 'deleted') {
|
||||||
this.props.dispatchRemoveItem({ section, id: body.resource.id });
|
this.props.dispatchRemoveItem({ section, id: body.resource.id });
|
||||||
|
|
||||||
repopulatePage('bookFileDeleted');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repopulate the page to handle recently imported file
|
// Repopulate the page to handle recently imported file
|
||||||
|
|||||||
@@ -15,5 +15,5 @@
|
|||||||
"start_url": "../../../../",
|
"start_url": "../../../../",
|
||||||
"theme_color": "#3a3f51",
|
"theme_color": "#3a3f51",
|
||||||
"background_color": "#3a3f51",
|
"background_color": "#3a3f51",
|
||||||
"display": "minimal-ui"
|
"display": "standalone"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
|
||||||
|
|
||||||
// This file contains some helpers for power users in a browser console
|
|
||||||
|
|
||||||
let hasWarned = false;
|
|
||||||
|
|
||||||
function checkActivationWarning() {
|
|
||||||
if (!hasWarned) {
|
|
||||||
console.log('Activated ReadarrApi console helpers.');
|
|
||||||
console.warn('Be warned: There will be no further confirmation checks.');
|
|
||||||
hasWarned = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function attachAsyncActions(promise) {
|
|
||||||
promise.filter = function() {
|
|
||||||
const args = arguments;
|
|
||||||
const res = this.then((d) => d.filter(...args));
|
|
||||||
attachAsyncActions(res);
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
promise.map = function() {
|
|
||||||
const args = arguments;
|
|
||||||
const res = this.then((d) => d.map(...args));
|
|
||||||
attachAsyncActions(res);
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
promise.all = function() {
|
|
||||||
const res = this.then((d) => Promise.all(d));
|
|
||||||
attachAsyncActions(res);
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
promise.forEach = function(action) {
|
|
||||||
const res = this.then((d) => Promise.all(d.map(action)));
|
|
||||||
attachAsyncActions(res);
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class ResourceApi {
|
|
||||||
constructor(api, url) {
|
|
||||||
this.api = api;
|
|
||||||
this.url = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
single(id) {
|
|
||||||
return this.api.fetch(`${this.url}/${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
all() {
|
|
||||||
return this.api.fetch(this.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
filter(pred) {
|
|
||||||
return this.all().filter(pred);
|
|
||||||
}
|
|
||||||
|
|
||||||
update(resource) {
|
|
||||||
return this.api.fetch(`${this.url}/${resource.id}`, { method: 'PUT', data: resource });
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(resource) {
|
|
||||||
if (typeof resource === 'object' && resource !== null && resource.id) {
|
|
||||||
resource = resource.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!resource || !Number.isInteger(resource)) {
|
|
||||||
throw Error('Invalid resource', resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.api.fetch(`${this.url}/${resource}`, { method: 'DELETE' });
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch(url, options) {
|
|
||||||
return this.api.fetch(`${this.url}${url}`, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConsoleApi {
|
|
||||||
constructor() {
|
|
||||||
this.author = new ResourceApi(this, '/author');
|
|
||||||
}
|
|
||||||
|
|
||||||
resource(url) {
|
|
||||||
return new ResourceApi(this, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch(url, options) {
|
|
||||||
checkActivationWarning();
|
|
||||||
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
const req = {
|
|
||||||
url,
|
|
||||||
method: options.method || 'GET'
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.data) {
|
|
||||||
req.dataType = 'json';
|
|
||||||
req.data = JSON.stringify(options.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
const promise = createAjaxRequest(req).request;
|
|
||||||
|
|
||||||
promise.fail((xhr) => {
|
|
||||||
console.error(`Failed to fetch ${url}`, xhr);
|
|
||||||
});
|
|
||||||
|
|
||||||
attachAsyncActions(promise);
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.ReadarrApi = new ConsoleApi();
|
|
||||||
|
|
||||||
export default ConsoleApi;
|
|
||||||
@@ -6,7 +6,6 @@ export const BOOKSHELF = 'bookshelf';
|
|||||||
export const KEY_VALUE_LIST = 'keyValueList';
|
export const KEY_VALUE_LIST = 'keyValueList';
|
||||||
export const MONITOR_BOOKS_SELECT = 'monitorBooksSelect';
|
export const MONITOR_BOOKS_SELECT = 'monitorBooksSelect';
|
||||||
export const MONITOR_NEW_ITEMS_SELECT = 'monitorNewItemsSelect';
|
export const MONITOR_NEW_ITEMS_SELECT = 'monitorNewItemsSelect';
|
||||||
export const FLOAT = 'float';
|
|
||||||
export const NUMBER = 'number';
|
export const NUMBER = 'number';
|
||||||
export const OAUTH = 'oauth';
|
export const OAUTH = 'oauth';
|
||||||
export const PASSWORD = 'password';
|
export const PASSWORD = 'password';
|
||||||
@@ -35,7 +34,6 @@ export const all = [
|
|||||||
KEY_VALUE_LIST,
|
KEY_VALUE_LIST,
|
||||||
MONITOR_BOOKS_SELECT,
|
MONITOR_BOOKS_SELECT,
|
||||||
MONITOR_NEW_ITEMS_SELECT,
|
MONITOR_NEW_ITEMS_SELECT,
|
||||||
FLOAT,
|
|
||||||
NUMBER,
|
NUMBER,
|
||||||
OAUTH,
|
OAUTH,
|
||||||
PASSWORD,
|
PASSWORD,
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Alert from 'Components/Alert';
|
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
import Table from 'Components/Table/Table';
|
import Table from 'Components/Table/Table';
|
||||||
import TableBody from 'Components/Table/TableBody';
|
import TableBody from 'Components/Table/TableBody';
|
||||||
import { icons, kinds, sortDirections } from 'Helpers/Props';
|
import { icons, sortDirections } from 'Helpers/Props';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import InteractiveSearchRow from './InteractiveSearchRow';
|
import InteractiveSearchRow from './InteractiveSearchRow';
|
||||||
import styles from './InteractiveSearch.css';
|
import styles from './InteractiveSearch.css';
|
||||||
@@ -113,17 +112,17 @@ function InteractiveSearch(props) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
!isFetching && isPopulated && !totalReleasesCount ?
|
!isFetching && isPopulated && !totalReleasesCount ?
|
||||||
<Alert kind={kinds.INFO}>
|
<div className={styles.blankpad}>
|
||||||
{translate('NoResultsFound')}
|
No results found
|
||||||
</Alert> :
|
</div> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!!totalReleasesCount && isPopulated && !items.length ?
|
!!totalReleasesCount && isPopulated && !items.length ?
|
||||||
<Alert kind={kinds.WARNING}>
|
<div className={styles.blankpad}>
|
||||||
{translate('AllResultsAreHiddenByTheAppliedFilter')}
|
All results are hidden by the applied filter
|
||||||
</Alert> :
|
</div> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +157,7 @@ function InteractiveSearch(props) {
|
|||||||
{
|
{
|
||||||
totalReleasesCount !== items.length && !!items.length ?
|
totalReleasesCount !== items.length && !!items.length ?
|
||||||
<div className={styles.filteredMessage}>
|
<div className={styles.filteredMessage}>
|
||||||
{translate('SomeResultsAreHiddenByTheAppliedFilter')}
|
Some results are hidden by the applied filter
|
||||||
</div> :
|
</div> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ class CustomFormat extends Component {
|
|||||||
isOpen={this.state.isDeleteCustomFormatModalOpen}
|
isOpen={this.state.isDeleteCustomFormatModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteCustomFormat')}
|
title={translate('DeleteCustomFormat')}
|
||||||
message={translate('DeleteCustomFormatMessageText', { name })}
|
message={translate('DeleteCustomFormatMessageText', [name])}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
isSpinning={isDeleting}
|
isSpinning={isDeleting}
|
||||||
onConfirm={this.onConfirmDeleteCustomFormat}
|
onConfirm={this.onConfirmDeleteCustomFormat}
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ class Specification extends Component {
|
|||||||
isOpen={this.state.isDeleteSpecificationModalOpen}
|
isOpen={this.state.isDeleteSpecificationModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteCondition')}
|
title={translate('DeleteCondition')}
|
||||||
message={translate('DeleteConditionMessageText', { name })}
|
message={translate('DeleteConditionMessageText', [name])}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
onConfirm={this.onConfirmDeleteSpecification}
|
onConfirm={this.onConfirmDeleteSpecification}
|
||||||
onCancel={this.onDeleteSpecificationModalClose}
|
onCancel={this.onDeleteSpecificationModalClose}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ class DownloadClient extends Component {
|
|||||||
isOpen={this.state.isDeleteDownloadClientModalOpen}
|
isOpen={this.state.isDeleteDownloadClientModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteDownloadClient')}
|
title={translate('DeleteDownloadClient')}
|
||||||
message={translate('DeleteDownloadClientMessageText', { name })}
|
message={translate('DeleteDownloadClientMessageText', [name])}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
onConfirm={this.onConfirmDeleteDownloadClient}
|
onConfirm={this.onConfirmDeleteDownloadClient}
|
||||||
onCancel={this.onDeleteDownloadClientModalClose}
|
onCancel={this.onDeleteDownloadClientModalClose}
|
||||||
|
|||||||
+1
-1
@@ -180,7 +180,7 @@ function ManageDownloadClientsEditModalContent(
|
|||||||
|
|
||||||
<ModalFooter className={styles.modalFooter}>
|
<ModalFooter className={styles.modalFooter}>
|
||||||
<div className={styles.selected}>
|
<div className={styles.selected}>
|
||||||
{translate('CountDownloadClientsSelected', { selectedCount })}
|
{translate('CountDownloadClientsSelected', [selectedCount])}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
+3
-3
@@ -286,9 +286,9 @@ function ManageDownloadClientsModalContent(
|
|||||||
isOpen={isDeleteModalOpen}
|
isOpen={isDeleteModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteSelectedDownloadClients')}
|
title={translate('DeleteSelectedDownloadClients')}
|
||||||
message={translate('DeleteSelectedDownloadClientsMessageText', {
|
message={translate('DeleteSelectedDownloadClientsMessageText', [
|
||||||
count: selectedIds.length,
|
selectedIds.length,
|
||||||
})}
|
])}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
onConfirm={onConfirmDelete}
|
onConfirm={onConfirmDelete}
|
||||||
onCancel={onDeleteModalClose}
|
onCancel={onDeleteModalClose}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Alert from 'Components/Alert';
|
|
||||||
import FieldSet from 'Components/FieldSet';
|
import FieldSet from 'Components/FieldSet';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
|
|
||||||
import PageSectionContent from 'Components/Page/PageSectionContent';
|
import PageSectionContent from 'Components/Page/PageSectionContent';
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import EditRemotePathMappingModalConnector from './EditRemotePathMappingModalConnector';
|
import EditRemotePathMappingModalConnector from './EditRemotePathMappingModalConnector';
|
||||||
import RemotePathMapping from './RemotePathMapping';
|
import RemotePathMapping from './RemotePathMapping';
|
||||||
@@ -52,11 +50,6 @@ class RemotePathMappings extends Component {
|
|||||||
errorMessage={translate('UnableToLoadRemotePathMappings')}
|
errorMessage={translate('UnableToLoadRemotePathMappings')}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
|
|
||||||
<Alert kind={kinds.INFO}>
|
|
||||||
<InlineMarkdown data={translate('RemotePathMappingsInfo', { app: 'Readarr', wikiLink: 'https://wiki.servarr.com/readarr/settings#remote-path-mappings' })} />
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<div className={styles.remotePathMappingsHeader}>
|
<div className={styles.remotePathMappingsHeader}>
|
||||||
<div className={styles.host}>
|
<div className={styles.host}>
|
||||||
{translate('Host')}
|
{translate('Host')}
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ class GeneralSettings extends Component {
|
|||||||
isResettingApiKey,
|
isResettingApiKey,
|
||||||
isWindows,
|
isWindows,
|
||||||
isWindowsService,
|
isWindowsService,
|
||||||
|
isDocker,
|
||||||
mode,
|
mode,
|
||||||
packageUpdateMechanism,
|
packageUpdateMechanism,
|
||||||
onInputChange,
|
onInputChange,
|
||||||
@@ -170,6 +171,7 @@ class GeneralSettings extends Component {
|
|||||||
settings={settings}
|
settings={settings}
|
||||||
isWindows={isWindows}
|
isWindows={isWindows}
|
||||||
packageUpdateMechanism={packageUpdateMechanism}
|
packageUpdateMechanism={packageUpdateMechanism}
|
||||||
|
isDocker={isDocker}
|
||||||
onInputChange={onInputChange}
|
onInputChange={onInputChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -212,6 +214,7 @@ GeneralSettings.propTypes = {
|
|||||||
hasSettings: PropTypes.bool.isRequired,
|
hasSettings: PropTypes.bool.isRequired,
|
||||||
isWindows: PropTypes.bool.isRequired,
|
isWindows: PropTypes.bool.isRequired,
|
||||||
isWindowsService: PropTypes.bool.isRequired,
|
isWindowsService: PropTypes.bool.isRequired,
|
||||||
|
isDocker: PropTypes.bool.isRequired,
|
||||||
mode: PropTypes.string.isRequired,
|
mode: PropTypes.string.isRequired,
|
||||||
packageUpdateMechanism: PropTypes.string.isRequired,
|
packageUpdateMechanism: PropTypes.string.isRequired,
|
||||||
onInputChange: PropTypes.func.isRequired,
|
onInputChange: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ function createMapStateToProps() {
|
|||||||
isResettingApiKey,
|
isResettingApiKey,
|
||||||
isWindows: systemStatus.isWindows,
|
isWindows: systemStatus.isWindows,
|
||||||
isWindowsService: systemStatus.isWindows && systemStatus.mode === 'service',
|
isWindowsService: systemStatus.isWindows && systemStatus.mode === 'service',
|
||||||
|
isDocker: systemStatus.isDocker,
|
||||||
mode: systemStatus.mode,
|
mode: systemStatus.mode,
|
||||||
packageUpdateMechanism: systemStatus.packageUpdateMechanism,
|
packageUpdateMechanism: systemStatus.packageUpdateMechanism,
|
||||||
...sectionSettings
|
...sectionSettings
|
||||||
|
|||||||
@@ -8,17 +8,12 @@ import { inputTypes, sizes } from 'Helpers/Props';
|
|||||||
import titleCase from 'Utilities/String/titleCase';
|
import titleCase from 'Utilities/String/titleCase';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
const branchValues = [
|
|
||||||
'master',
|
|
||||||
'develop',
|
|
||||||
'nightly'
|
|
||||||
];
|
|
||||||
|
|
||||||
function UpdateSettings(props) {
|
function UpdateSettings(props) {
|
||||||
const {
|
const {
|
||||||
advancedSettings,
|
advancedSettings,
|
||||||
settings,
|
settings,
|
||||||
isWindows,
|
isWindows,
|
||||||
|
isDocker,
|
||||||
packageUpdateMechanism,
|
packageUpdateMechanism,
|
||||||
onInputChange
|
onInputChange
|
||||||
} = props;
|
} = props;
|
||||||
@@ -49,21 +44,32 @@ function UpdateSettings(props) {
|
|||||||
|
|
||||||
updateOptions.push({ key: 'script', value: 'Script' });
|
updateOptions.push({ key: 'script', value: 'Script' });
|
||||||
|
|
||||||
|
if (isDocker) {
|
||||||
|
return (
|
||||||
|
<FieldSet legend={translate('Updates')}>
|
||||||
|
<div>
|
||||||
|
{translate('UpdatingIsDisabledInsideADockerContainerUpdateTheContainerImageInstead')}
|
||||||
|
</div>
|
||||||
|
</FieldSet>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldSet legend={translate('Updates')}>
|
<FieldSet legend={translate('Updates')}>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
advancedSettings={advancedSettings}
|
advancedSettings={advancedSettings}
|
||||||
isAdvanced={true}
|
isAdvanced={true}
|
||||||
>
|
>
|
||||||
<FormLabel>{translate('Branch')}</FormLabel>
|
<FormLabel>
|
||||||
|
{translate('Branch')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.AUTO_COMPLETE}
|
type={inputTypes.TEXT}
|
||||||
name="branch"
|
name="branch"
|
||||||
helpText={usingExternalUpdateMechanism ? translate('UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism') : translate('UsingExternalUpdateMechanismBranchToUseToUpdateReadarr')}
|
helpText={usingExternalUpdateMechanism ? translate('UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism') : translate('UsingExternalUpdateMechanismBranchToUseToUpdateReadarr')}
|
||||||
helpLink="https://wiki.servarr.com/readarr/faq#how-do-I-update-my-readarr"
|
helpLink="https://wiki.servarr.com/readarr/faq#how-do-I-update-my-readarr"
|
||||||
{...branch}
|
{...branch}
|
||||||
values={branchValues}
|
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
readOnly={usingExternalUpdateMechanism}
|
readOnly={usingExternalUpdateMechanism}
|
||||||
/>
|
/>
|
||||||
@@ -77,13 +83,14 @@ function UpdateSettings(props) {
|
|||||||
isAdvanced={true}
|
isAdvanced={true}
|
||||||
size={sizes.MEDIUM}
|
size={sizes.MEDIUM}
|
||||||
>
|
>
|
||||||
<FormLabel>{translate('Automatic')}</FormLabel>
|
<FormLabel>
|
||||||
|
{translate('Automatic')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="updateAutomatically"
|
name="updateAutomatically"
|
||||||
helpText={translate('UpdateAutomaticallyHelpText')}
|
helpText={translate('UpdateAutomaticallyHelpText')}
|
||||||
helpTextWarning={updateMechanism.value === 'docker' ? translate('AutomaticUpdatesDisabledDocker', { appName: 'Readarr' }) : undefined}
|
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...updateAutomatically}
|
{...updateAutomatically}
|
||||||
/>
|
/>
|
||||||
@@ -93,7 +100,9 @@ function UpdateSettings(props) {
|
|||||||
advancedSettings={advancedSettings}
|
advancedSettings={advancedSettings}
|
||||||
isAdvanced={true}
|
isAdvanced={true}
|
||||||
>
|
>
|
||||||
<FormLabel>{translate('Mechanism')}</FormLabel>
|
<FormLabel>
|
||||||
|
{translate('Mechanism')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.SELECT}
|
type={inputTypes.SELECT}
|
||||||
@@ -112,7 +121,9 @@ function UpdateSettings(props) {
|
|||||||
advancedSettings={advancedSettings}
|
advancedSettings={advancedSettings}
|
||||||
isAdvanced={true}
|
isAdvanced={true}
|
||||||
>
|
>
|
||||||
<FormLabel>{translate('ScriptPath')}</FormLabel>
|
<FormLabel>
|
||||||
|
{translate('ScriptPath')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.TEXT}
|
type={inputTypes.TEXT}
|
||||||
@@ -133,6 +144,7 @@ UpdateSettings.propTypes = {
|
|||||||
advancedSettings: PropTypes.bool.isRequired,
|
advancedSettings: PropTypes.bool.isRequired,
|
||||||
settings: PropTypes.object.isRequired,
|
settings: PropTypes.object.isRequired,
|
||||||
isWindows: PropTypes.bool.isRequired,
|
isWindows: PropTypes.bool.isRequired,
|
||||||
|
isDocker: PropTypes.bool.isRequired,
|
||||||
packageUpdateMechanism: PropTypes.string.isRequired,
|
packageUpdateMechanism: PropTypes.string.isRequired,
|
||||||
onInputChange: PropTypes.func.isRequired
|
onInputChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ class ImportList extends Component {
|
|||||||
isOpen={this.state.isDeleteImportListModalOpen}
|
isOpen={this.state.isDeleteImportListModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteImportList')}
|
title={translate('DeleteImportList')}
|
||||||
message={translate('DeleteImportListMessageText', { name })}
|
message={translate('DeleteImportListMessageText', [name])}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
onConfirm={this.onConfirmDeleteImportList}
|
onConfirm={this.onConfirmDeleteImportList}
|
||||||
onCancel={this.onDeleteImportListModalClose}
|
onCancel={this.onDeleteImportListModalClose}
|
||||||
|
|||||||
+1
-1
@@ -184,7 +184,7 @@ function ManageImportListsEditModalContent(
|
|||||||
|
|
||||||
<ModalFooter className={styles.modalFooter}>
|
<ModalFooter className={styles.modalFooter}>
|
||||||
<div className={styles.selected}>
|
<div className={styles.selected}>
|
||||||
{translate('CountImportListsSelected', { selectedCount })}
|
{translate('CountImportListsSelected', [selectedCount])}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
+3
-3
@@ -283,9 +283,9 @@ function ManageImportListsModalContent(
|
|||||||
isOpen={isDeleteModalOpen}
|
isOpen={isDeleteModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteSelectedImportLists')}
|
title={translate('DeleteSelectedImportLists')}
|
||||||
message={translate('DeleteSelectedImportListsMessageText', {
|
message={translate('DeleteSelectedImportListsMessageText', [
|
||||||
count: selectedIds.length,
|
selectedIds.length,
|
||||||
})}
|
])}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
onConfirm={onConfirmDelete}
|
onConfirm={onConfirmDelete}
|
||||||
onCancel={onDeleteModalClose}
|
onCancel={onDeleteModalClose}
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ class Indexer extends Component {
|
|||||||
isOpen={this.state.isDeleteIndexerModalOpen}
|
isOpen={this.state.isDeleteIndexerModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteIndexer')}
|
title={translate('DeleteIndexer')}
|
||||||
message={translate('DeleteIndexerMessageText', { name })}
|
message={translate('DeleteIndexerMessageText', [name])}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
onConfirm={this.onConfirmDeleteIndexer}
|
onConfirm={this.onConfirmDeleteIndexer}
|
||||||
onCancel={this.onDeleteIndexerModalClose}
|
onCancel={this.onDeleteIndexerModalClose}
|
||||||
|
|||||||
+1
-1
@@ -178,7 +178,7 @@ function ManageIndexersEditModalContent(
|
|||||||
|
|
||||||
<ModalFooter className={styles.modalFooter}>
|
<ModalFooter className={styles.modalFooter}>
|
||||||
<div className={styles.selected}>
|
<div className={styles.selected}>
|
||||||
{translate('CountIndexersSelected', { selectedCount })}
|
{translate('CountIndexersSelected', [selectedCount])}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -281,9 +281,9 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
|
|||||||
isOpen={isDeleteModalOpen}
|
isOpen={isDeleteModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteSelectedIndexers')}
|
title={translate('DeleteSelectedIndexers')}
|
||||||
message={translate('DeleteSelectedIndexersMessageText', {
|
message={translate('DeleteSelectedIndexersMessageText', [
|
||||||
count: selectedIds.length,
|
selectedIds.length,
|
||||||
})}
|
])}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
onConfirm={onConfirmDelete}
|
onConfirm={onConfirmDelete}
|
||||||
onCancel={onDeleteModalClose}
|
onCancel={onDeleteModalClose}
|
||||||
|
|||||||
@@ -212,24 +212,26 @@ class MediaManagement extends Component {
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
{
|
{
|
||||||
settings.importExtraFiles.value ?
|
settings.importExtraFiles.value &&
|
||||||
<FormGroup
|
<FormGroup
|
||||||
advancedSettings={advancedSettings}
|
advancedSettings={advancedSettings}
|
||||||
isAdvanced={true}
|
isAdvanced={true}
|
||||||
>
|
>
|
||||||
<FormLabel>{translate('ImportExtraFiles')}</FormLabel>
|
<FormLabel>
|
||||||
|
{translate('ImportExtraFiles')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.TEXT}
|
type={inputTypes.TEXT}
|
||||||
name="extraFileExtensions"
|
name="extraFileExtensions"
|
||||||
helpTexts={[
|
helpTexts={[
|
||||||
translate('ExtraFileExtensionsHelpText'),
|
translate('ExtraFileExtensionsHelpTexts1'),
|
||||||
translate('ExtraFileExtensionsHelpTextsExamples')
|
translate('ExtraFileExtensionsHelpTexts2')
|
||||||
]}
|
]}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...settings.extraFileExtensions}
|
{...settings.extraFileExtensions}
|
||||||
/>
|
/>
|
||||||
</FormGroup> : null
|
</FormGroup>
|
||||||
}
|
}
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class RootFolder extends Component {
|
|||||||
isOpen={this.state.isDeleteRootFolderModalOpen}
|
isOpen={this.state.isDeleteRootFolderModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteRootFolder')}
|
title={translate('DeleteRootFolder')}
|
||||||
message={translate('DeleteRootFolderMessageText', { name })}
|
message={translate('DeleteRootFolderMessageText', [name])}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
onConfirm={this.onConfirmDeleteRootFolder}
|
onConfirm={this.onConfirmDeleteRootFolder}
|
||||||
onCancel={this.onDeleteRootFolderModalClose}
|
onCancel={this.onDeleteRootFolderModalClose}
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ class Notification extends Component {
|
|||||||
onReleaseImport,
|
onReleaseImport,
|
||||||
onUpgrade,
|
onUpgrade,
|
||||||
onRename,
|
onRename,
|
||||||
onAuthorAdded,
|
|
||||||
onAuthorDelete,
|
onAuthorDelete,
|
||||||
onBookDelete,
|
onBookDelete,
|
||||||
onBookFileDelete,
|
onBookFileDelete,
|
||||||
@@ -74,7 +73,6 @@ class Notification extends Component {
|
|||||||
supportsOnReleaseImport,
|
supportsOnReleaseImport,
|
||||||
supportsOnUpgrade,
|
supportsOnUpgrade,
|
||||||
supportsOnRename,
|
supportsOnRename,
|
||||||
supportsOnAuthorAdded,
|
|
||||||
supportsOnAuthorDelete,
|
supportsOnAuthorDelete,
|
||||||
supportsOnBookDelete,
|
supportsOnBookDelete,
|
||||||
supportsOnBookFileDelete,
|
supportsOnBookFileDelete,
|
||||||
@@ -138,14 +136,6 @@ 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}>
|
||||||
@@ -237,7 +227,7 @@ class Notification extends Component {
|
|||||||
isOpen={this.state.isDeleteNotificationModalOpen}
|
isOpen={this.state.isDeleteNotificationModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteNotification')}
|
title={translate('DeleteNotification')}
|
||||||
message={translate('DeleteNotificationMessageText', { name })}
|
message={translate('DeleteNotificationMessageText', [name])}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
onConfirm={this.onConfirmDeleteNotification}
|
onConfirm={this.onConfirmDeleteNotification}
|
||||||
onCancel={this.onDeleteNotificationModalClose}
|
onCancel={this.onDeleteNotificationModalClose}
|
||||||
@@ -254,7 +244,6 @@ 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,
|
||||||
@@ -268,7 +257,6 @@ 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,7 +19,6 @@ function NotificationEventItems(props) {
|
|||||||
onReleaseImport,
|
onReleaseImport,
|
||||||
onUpgrade,
|
onUpgrade,
|
||||||
onRename,
|
onRename,
|
||||||
onAuthorAdded,
|
|
||||||
onAuthorDelete,
|
onAuthorDelete,
|
||||||
onBookDelete,
|
onBookDelete,
|
||||||
onBookFileDelete,
|
onBookFileDelete,
|
||||||
@@ -33,7 +32,6 @@ function NotificationEventItems(props) {
|
|||||||
supportsOnReleaseImport,
|
supportsOnReleaseImport,
|
||||||
supportsOnUpgrade,
|
supportsOnUpgrade,
|
||||||
supportsOnRename,
|
supportsOnRename,
|
||||||
supportsOnAuthorAdded,
|
|
||||||
supportsOnAuthorDelete,
|
supportsOnAuthorDelete,
|
||||||
supportsOnBookDelete,
|
supportsOnBookDelete,
|
||||||
supportsOnBookFileDelete,
|
supportsOnBookFileDelete,
|
||||||
@@ -125,17 +123,6 @@ 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}
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ class MetadataProfile extends Component {
|
|||||||
isOpen={this.state.isDeleteMetadataProfileModalOpen}
|
isOpen={this.state.isDeleteMetadataProfileModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteMetadataProfile')}
|
title={translate('DeleteMetadataProfile')}
|
||||||
message={translate('DeleteMetadataProfileMessageText', { name })}
|
message={translate('DeleteMetadataProfileMessageText', [name])}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
isSpinning={isDeleting}
|
isSpinning={isDeleting}
|
||||||
onConfirm={this.onConfirmDeleteMetadataProfile}
|
onConfirm={this.onConfirmDeleteMetadataProfile}
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ class QualityProfile extends Component {
|
|||||||
isOpen={this.state.isDeleteQualityProfileModalOpen}
|
isOpen={this.state.isDeleteQualityProfileModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteQualityProfile')}
|
title={translate('DeleteQualityProfile')}
|
||||||
message={translate('DeleteQualityProfileMessageText', { name })}
|
message={translate('DeleteQualityProfileMessageText', [name])}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
isSpinning={isDeleting}
|
isSpinning={isDeleting}
|
||||||
onConfirm={this.onConfirmDeleteQualityProfile}
|
onConfirm={this.onConfirmDeleteQualityProfile}
|
||||||
|
|||||||
@@ -106,7 +106,6 @@ 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;
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHand
|
|||||||
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
|
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
|
||||||
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
||||||
import { createThunk } from 'Store/thunks';
|
import { createThunk } from 'Store/thunks';
|
||||||
import monitorNewItemsOptions from 'Utilities/Author/monitorNewItemsOptions';
|
|
||||||
import monitorOptions from 'Utilities/Author/monitorOptions';
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Variables
|
// Variables
|
||||||
@@ -53,10 +51,6 @@ export default {
|
|||||||
port: 8080,
|
port: 8080,
|
||||||
useSsl: false,
|
useSsl: false,
|
||||||
outputProfile: 'default',
|
outputProfile: 'default',
|
||||||
defaultQualityProfileId: 0,
|
|
||||||
defaultMetadataProfileId: 0,
|
|
||||||
defaultMonitorOption: monitorOptions[0].key,
|
|
||||||
defaultNewItemMonitorOption: monitorNewItemsOptions[0].key,
|
|
||||||
defaultTags: []
|
defaultTags: []
|
||||||
},
|
},
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ class BackupRow extends Component {
|
|||||||
isOpen={isConfirmDeleteModalOpen}
|
isOpen={isConfirmDeleteModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteBackup')}
|
title={translate('DeleteBackup')}
|
||||||
message={translate('DeleteBackupMessageText', { name })}
|
message={translate('DeleteBackupMessageText', [name])}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
onConfirm={this.onConfirmDeletePress}
|
onConfirm={this.onConfirmDeletePress}
|
||||||
onCancel={this.onConfirmDeleteModalClose}
|
onCancel={this.onConfirmDeleteModalClose}
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ class RestoreBackupModalContent extends Component {
|
|||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
{
|
{
|
||||||
!!id && translate('WouldYouLikeToRestoreBackup', { name })
|
!!id && `Would you like to restore the backup '${name}'?`
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,14 +39,6 @@ function getInternalLink(source) {
|
|||||||
to="/settings/downloadclients"
|
to="/settings/downloadclients"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case 'NotificationStatusCheck':
|
|
||||||
return (
|
|
||||||
<IconButton
|
|
||||||
name={icons.SETTINGS}
|
|
||||||
title={translate('Settings')}
|
|
||||||
to="/settings/connect"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case 'RootFolderCheck':
|
case 'RootFolderCheck':
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -71,7 +63,6 @@ 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}
|
||||||
|
|||||||
@@ -25,19 +25,18 @@ export async function fetchTranslations(): Promise<boolean> {
|
|||||||
|
|
||||||
export default function translate(
|
export default function translate(
|
||||||
key: string,
|
key: string,
|
||||||
tokens: Record<string, string | number | boolean> = { appName: 'Readarr' }
|
args?: (string | number | boolean)[]
|
||||||
) {
|
) {
|
||||||
|
if (!(key in translations)) {
|
||||||
|
console.debug(key);
|
||||||
|
}
|
||||||
|
|
||||||
const translation = translations[key] || key;
|
const translation = translations[key] || key;
|
||||||
|
|
||||||
if (tokens) {
|
if (args) {
|
||||||
// Fallback to the old behaviour for translations not yet updated to use named tokens
|
return translation.replace(/\{(\d+)\}/g, (match, index) => {
|
||||||
Object.values(tokens).forEach((value, index) => {
|
return String(args[index]) ?? match;
|
||||||
tokens[index] = value;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return translation.replace(/\{([a-z0-9]+?)\}/gi, (match, tokenMatch) =>
|
|
||||||
String(tokens[tokenMatch] ?? match)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return translation;
|
return translation;
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class CutoffUnmetConnector extends Component {
|
|||||||
gotoCutoffUnmetFirstPage
|
gotoCutoffUnmetFirstPage
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
registerPagePopulator(this.repopulate, ['bookFileUpdated', 'bookFileDeleted']);
|
registerPagePopulator(this.repopulate, ['bookFileUpdated']);
|
||||||
|
|
||||||
if (useCurrentPage) {
|
if (useCurrentPage) {
|
||||||
fetchCutoffUnmet();
|
fetchCutoffUnmet();
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class MissingConnector extends Component {
|
|||||||
gotoMissingFirstPage
|
gotoMissingFirstPage
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
registerPagePopulator(this.repopulate, ['bookFileUpdated', 'bookFileDeleted']);
|
registerPagePopulator(this.repopulate, ['bookFileUpdated']);
|
||||||
|
|
||||||
if (useCurrentPage) {
|
if (useCurrentPage) {
|
||||||
fetchMissing();
|
fetchMissing();
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import { render } from 'react-dom';
|
|||||||
import createAppStore from 'Store/createAppStore';
|
import createAppStore from 'Store/createAppStore';
|
||||||
import App from './App/App';
|
import App from './App/App';
|
||||||
|
|
||||||
import 'Diag/ConsoleApi';
|
|
||||||
|
|
||||||
export async function bootstrap() {
|
export async function bootstrap() {
|
||||||
const history = createBrowserHistory();
|
const history = createBrowserHistory();
|
||||||
const store = createAppStore(history);
|
const store = createAppStore(history);
|
||||||
|
|||||||
+20
-18
@@ -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.25",
|
"@microsoft/signalr": "6.0.16",
|
||||||
"@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,28 +83,30 @@
|
|||||||
"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.11",
|
"@babel/core": "7.22.9",
|
||||||
"@babel/eslint-parser": "7.22.11",
|
"@babel/eslint-parser": "7.22.9",
|
||||||
|
"@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.15",
|
"@babel/preset-env": "7.22.9",
|
||||||
"@babel/preset-react": "7.22.5",
|
"@babel/preset-react": "7.22.5",
|
||||||
"@babel/preset-typescript": "7.22.11",
|
"@babel/preset-typescript": "7.22.5",
|
||||||
"@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.5.0",
|
"@typescript-eslint/eslint-plugin": "6.0.0",
|
||||||
"@typescript-eslint/parser": "6.5.0",
|
"@typescript-eslint/parser": "6.0.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.32.1",
|
"core-js": "3.31.1",
|
||||||
"css-loader": "6.8.1",
|
"css-loader": "6.7.3",
|
||||||
"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",
|
||||||
@@ -118,10 +120,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.3",
|
"html-webpack-plugin": "5.5.1",
|
||||||
"loader-utils": "^3.2.1",
|
"loader-utils": "^3.2.1",
|
||||||
"mini-css-extract-plugin": "2.7.6",
|
"mini-css-extract-plugin": "2.7.5",
|
||||||
"postcss": "8.4.31",
|
"postcss": "8.4.23",
|
||||||
"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",
|
||||||
@@ -133,14 +135,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.3",
|
"style-loader": "3.3.2",
|
||||||
"stylelint": "15.10.3",
|
"stylelint": "15.10.1",
|
||||||
"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.4",
|
"ts-loader": "9.4.3",
|
||||||
"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.2",
|
"webpack": "5.88.1",
|
||||||
"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"
|
||||||
|
|||||||
@@ -44,16 +44,16 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
|
|||||||
|
|
||||||
[Tasks]
|
[Tasks]
|
||||||
Name: "desktopIcon"; Description: "{cm:CreateDesktopIcon}"
|
Name: "desktopIcon"; Description: "{cm:CreateDesktopIcon}"
|
||||||
Name: "windowsService"; Description: "Install Windows Service (Starts when the computer starts as the LocalService user, you will need to change the user to access network shares)"; GroupDescription: "Start automatically"; Flags: exclusive
|
Name: "windowsService"; Description: "Install Windows Service (Starts when the computer starts as the LocalService user, you will need to change the user to access network shares)"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
|
||||||
Name: "startupShortcut"; Description: "Create shortcut in Startup folder (Starts when you log into Windows)"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
|
Name: "startupShortcut"; Description: "Create shortcut in Startup folder (Starts when you log into Windows)"; GroupDescription: "Start automatically"; Flags: exclusive
|
||||||
Name: "none"; Description: "Do not start automatically"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
|
Name: "none"; Description: "Do not start automatically"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
|
||||||
|
|
||||||
[Dirs]
|
[Dirs]
|
||||||
Name: "{app}"; Permissions: users-modify
|
Name: "{app}"; Permissions: users-modify
|
||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
Source: "..\..\..\_artifacts\{#Runtime}\{#Framework}\Readarr\Readarr.exe"; DestDir: "{app}\bin"; Flags: ignoreversion
|
Source: "..\_artifacts\{#Runtime}\{#Framework}\Readarr\Readarr.exe"; DestDir: "{app}\bin"; Flags: ignoreversion
|
||||||
Source: "..\..\..\_artifacts\{#Runtime}\{#Framework}\Readarr\*"; Excludes: "Readarr.Update"; DestDir: "{app}\bin"; Flags: ignoreversion recursesubdirs createallsubdirs
|
Source: "..\_artifacts\{#Runtime}\{#Framework}\Readarr\*"; Excludes: "Readarr.Update"; DestDir: "{app}\bin"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||||
|
|
||||||
[Icons]
|
[Icons]
|
||||||
@@ -72,13 +72,12 @@ Filename: "{app}\bin\Readarr.exe"; Description: "Open Readarr Web UI"; Flags: po
|
|||||||
Filename: "{app}\bin\Readarr.exe"; Description: "Start Readarr"; Flags: postinstall skipifsilent nowait; Tasks: startupShortcut none;
|
Filename: "{app}\bin\Readarr.exe"; Description: "Start Readarr"; Flags: postinstall skipifsilent nowait; Tasks: startupShortcut none;
|
||||||
|
|
||||||
[UninstallRun]
|
[UninstallRun]
|
||||||
Filename: "{app}\bin\readarr.console.exe"; Parameters: "/u"; Flags: waituntilterminated skipifdoesntexist
|
Filename: "{app}\bin\Readarr.Console.exe"; Parameters: "/u"; Flags: waituntilterminated skipifdoesntexist
|
||||||
|
|
||||||
[Code]
|
[Code]
|
||||||
function PrepareToInstall(var NeedsRestart: Boolean): String;
|
function PrepareToInstall(var NeedsRestart: Boolean): String;
|
||||||
var
|
var
|
||||||
ResultCode: Integer;
|
ResultCode: Integer;
|
||||||
begin
|
begin
|
||||||
Exec('net', 'stop readarr', '', 0, ewWaitUntilTerminated, ResultCode)
|
Exec(ExpandConstant('{commonappdata}\Readarr\bin\Readarr.Console.exe'), '/u', '', 0, ewWaitUntilTerminated, ResultCode)
|
||||||
Exec('sc', 'delete readarr', '', 0, ewWaitUntilTerminated, ResultCode)
|
|
||||||
end;
|
end;
|
||||||
@@ -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.3" />
|
<PackageVersion Include="DryIoc.dll" Version="5.4.0" />
|
||||||
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" />
|
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" />
|
||||||
<PackageVersion Include="Equ" Version="2.3.0" />
|
<PackageVersion Include="Equ" Version="2.3.0" />
|
||||||
<PackageVersion Include="FluentAssertions" Version="5.10.3" />
|
<PackageVersion Include="FluentAssertions" Version="5.10.3" />
|
||||||
@@ -16,11 +16,11 @@
|
|||||||
<PackageVersion Include="ImpromptuInterface" Version="7.0.1" />
|
<PackageVersion Include="ImpromptuInterface" Version="7.0.1" />
|
||||||
<PackageVersion Include="LazyCache" Version="2.4.0" />
|
<PackageVersion Include="LazyCache" Version="2.4.0" />
|
||||||
<PackageVersion Include="Mailkit" Version="3.6.0" />
|
<PackageVersion Include="Mailkit" Version="3.6.0" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.25" />
|
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.16" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" />
|
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.1" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||||
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
<PackageVersion Include="NLog.Extensions.Logging" Version="5.2.3" />
|
<PackageVersion Include="NLog.Extensions.Logging" Version="5.2.3" />
|
||||||
<PackageVersion Include="NLog" Version="5.1.4" />
|
<PackageVersion Include="NLog" Version="5.1.4" />
|
||||||
<PackageVersion Include="NLog.Targets.Syslog" Version="7.0.0" />
|
<PackageVersion Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||||
<PackageVersion Include="Npgsql" Version="7.0.6" />
|
<PackageVersion Include="Npgsql" Version="6.0.9" />
|
||||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" />
|
<PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||||
<PackageVersion Include="NunitXml.TestLogger" Version="3.0.117" />
|
<PackageVersion Include="NunitXml.TestLogger" Version="3.0.117" />
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
|
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
|
||||||
<PackageVersion Include="Sentry" Version="3.31.0" />
|
<PackageVersion Include="Sentry" Version="3.31.0" />
|
||||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.0.2" />
|
<PackageVersion Include="SixLabors.ImageSharp" Version="3.0.1" />
|
||||||
<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.1" />
|
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="6.0.0" />
|
||||||
<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.9" />
|
<PackageVersion Include="System.Text.Json" Version="6.0.7" />
|
||||||
<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>
|
||||||
|
|||||||
@@ -40,10 +40,6 @@ namespace NzbDrone.Automation.Test.PageModel
|
|||||||
var element = d.FindElement(By.ClassName("followingBalls"));
|
var element = d.FindElement(By.ClassName("followingBalls"));
|
||||||
return !element.Displayed;
|
return !element.Displayed;
|
||||||
}
|
}
|
||||||
catch (StaleElementReferenceException)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (NoSuchElementException)
|
catch (NoSuchElementException)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -838,7 +838,7 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
|
|
||||||
// Note: never returns anything.
|
// Note: never returns anything.
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(v => v.GetFileInfos(It.IsAny<string>(), It.IsAny<bool>()))
|
.Setup(v => v.GetFileInfos(It.IsAny<string>(), It.IsAny<SearchOption>()))
|
||||||
.Returns(new List<IFileInfo>());
|
.Returns(new List<IFileInfo>());
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
@@ -878,8 +878,8 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
.Returns<string>(v => fileSystem.DirectoryInfo.FromDirectoryName(v).GetDirectories().ToList());
|
.Returns<string>(v => fileSystem.DirectoryInfo.FromDirectoryName(v).GetDirectories().ToList());
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(v => v.GetFileInfos(It.IsAny<string>(), It.IsAny<bool>()))
|
.Setup(v => v.GetFileInfos(It.IsAny<string>(), It.IsAny<SearchOption>()))
|
||||||
.Returns((string v, bool recursive) => fileSystem.DirectoryInfo.FromDirectoryName(v).GetFiles("*", new EnumerationOptions { RecurseSubdirectories = recursive }).ToList());
|
.Returns((string v, SearchOption option) => fileSystem.DirectoryInfo.FromDirectoryName(v).GetFiles("*", option).ToList());
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(v => v.GetFileSize(It.IsAny<string>()))
|
.Setup(v => v.GetFileSize(It.IsAny<string>()))
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ 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;
|
||||||
@@ -115,21 +114,21 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_execute_simple_get()
|
public void should_execute_simple_get()
|
||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||||
|
|
||||||
var response = await Subject.ExecuteAsync(request);
|
var response = Subject.Execute(request);
|
||||||
|
|
||||||
response.Content.Should().NotBeNullOrWhiteSpace();
|
response.Content.Should().NotBeNullOrWhiteSpace();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_execute_https_get()
|
public void should_execute_https_get()
|
||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||||
|
|
||||||
var response = await Subject.ExecuteAsync(request);
|
var response = Subject.Execute(request);
|
||||||
|
|
||||||
response.Content.Should().NotBeNullOrWhiteSpace();
|
response.Content.Should().NotBeNullOrWhiteSpace();
|
||||||
}
|
}
|
||||||
@@ -141,47 +140,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.ThrowsAsync<HttpRequestException>(async () => await Subject.ExecuteAsync(request));
|
Assert.Throws<HttpRequestException>(() => Subject.Execute(request));
|
||||||
ExceptionVerification.ExpectedErrors(1);
|
ExceptionVerification.ExpectedErrors(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task bad_ssl_should_pass_if_remote_validation_disabled()
|
public void 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");
|
||||||
|
|
||||||
await Subject.ExecuteAsync(request);
|
Subject.Execute(request);
|
||||||
ExceptionVerification.ExpectedErrors(0);
|
ExceptionVerification.ExpectedErrors(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_execute_typed_get()
|
public void should_execute_typed_get()
|
||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/get?test=1");
|
var request = new HttpRequest($"https://{_httpBinHost}/get?test=1");
|
||||||
|
|
||||||
var response = await Subject.GetAsync<HttpBinResource>(request);
|
var response = Subject.Get<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 async Task should_execute_simple_post()
|
public void 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 = await Subject.PostAsync<HttpBinResource>(request);
|
var response = Subject.Post<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Data.Should().Be(message);
|
response.Resource.Data.Should().Be(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_execute_post_with_content_type()
|
public void should_execute_post_with_content_type()
|
||||||
{
|
{
|
||||||
var message = "{ my: 1 }";
|
var message = "{ my: 1 }";
|
||||||
|
|
||||||
@@ -189,16 +188,17 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
request.SetContent(message);
|
request.SetContent(message);
|
||||||
request.Headers.ContentType = "application/json";
|
request.Headers.ContentType = "application/json";
|
||||||
|
|
||||||
var response = await Subject.PostAsync<HttpBinResource>(request);
|
var response = Subject.Post<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Data.Should().Be(message);
|
response.Resource.Data.Should().Be(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_execute_get_using_gzip()
|
public void 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,10 +208,11 @@ 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 async Task should_execute_get_using_brotli()
|
public void 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");
|
||||||
|
|
||||||
@@ -229,7 +230,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.ThrowsAsync<HttpException>(async () => await Subject.GetAsync<HttpBinResource>(request));
|
var exception = Assert.Throws<HttpException>(() => Subject.Get<HttpBinResource>(request));
|
||||||
|
|
||||||
((int)exception.Response.StatusCode).Should().Be(statusCode);
|
((int)exception.Response.StatusCode).Should().Be(statusCode);
|
||||||
|
|
||||||
@@ -242,7 +243,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 };
|
||||||
|
|
||||||
Assert.ThrowsAsync<HttpException>(async () => await Subject.GetAsync<HttpBinResource>(request));
|
var exception = Assert.Throws<HttpException>(() => Subject.Get<HttpBinResource>(request));
|
||||||
|
|
||||||
ExceptionVerification.IgnoreWarns();
|
ExceptionVerification.IgnoreWarns();
|
||||||
}
|
}
|
||||||
@@ -252,7 +253,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.ThrowsAsync<HttpException>(async () => await Subject.GetAsync<HttpBinResource>(request));
|
var exception = Assert.Throws<HttpException>(() => Subject.Get<HttpBinResource>(request));
|
||||||
|
|
||||||
ExceptionVerification.ExpectedWarns(1);
|
ExceptionVerification.ExpectedWarns(1);
|
||||||
}
|
}
|
||||||
@@ -263,28 +264,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;
|
||||||
|
|
||||||
Assert.ThrowsAsync<HttpException>(async () => await Subject.GetAsync<HttpBinResource>(request));
|
var exception = Assert.Throws<HttpException>(() => Subject.Get<HttpBinResource>(request));
|
||||||
|
|
||||||
ExceptionVerification.ExpectedWarns(0);
|
ExceptionVerification.ExpectedWarns(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_not_follow_redirects_when_not_in_production()
|
public void should_not_follow_redirects_when_not_in_production()
|
||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/redirect/1");
|
var request = new HttpRequest($"https://{_httpBinHost}/redirect/1");
|
||||||
|
|
||||||
await Subject.GetAsync(request);
|
Subject.Get(request);
|
||||||
|
|
||||||
ExceptionVerification.ExpectedErrors(1);
|
ExceptionVerification.ExpectedErrors(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_follow_redirects()
|
public void 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 = await Subject.GetAsync(request);
|
var response = Subject.Get(request);
|
||||||
|
|
||||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||||
|
|
||||||
@@ -292,12 +293,12 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_not_follow_redirects()
|
public void 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 = await Subject.GetAsync(request);
|
var response = Subject.Get(request);
|
||||||
|
|
||||||
response.StatusCode.Should().Be(HttpStatusCode.Found);
|
response.StatusCode.Should().Be(HttpStatusCode.Found);
|
||||||
|
|
||||||
@@ -305,14 +306,14 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_follow_redirects_to_https()
|
public void 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 = await Subject.GetAsync(request);
|
var response = Subject.Get(request);
|
||||||
|
|
||||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||||
response.Content.Should().Contain("Readarr");
|
response.Content.Should().Contain("Readarr");
|
||||||
@@ -326,17 +327,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.ThrowsAsync<WebException>(async () => await Subject.GetAsync(request));
|
Assert.Throws<WebException>(() => Subject.Get(request));
|
||||||
|
|
||||||
ExceptionVerification.ExpectedErrors(0);
|
ExceptionVerification.ExpectedErrors(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_send_user_agent()
|
public void should_send_user_agent()
|
||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||||
|
|
||||||
var response = await Subject.GetAsync<HttpBinResource>(request);
|
var response = Subject.Get<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Headers.Should().ContainKey("User-Agent");
|
response.Resource.Headers.Should().ContainKey("User-Agent");
|
||||||
|
|
||||||
@@ -346,24 +347,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 async Task should_send_headers(string header, string value)
|
public void 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 = await Subject.GetAsync<HttpBinResource>(request);
|
var response = Subject.Get<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Headers[header].ToString().Should().Be(value);
|
response.Resource.Headers[header].ToString().Should().Be(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_download_file()
|
public void 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";
|
||||||
|
|
||||||
await Subject.DownloadFileAsync(url, file);
|
Subject.DownloadFile(url, file);
|
||||||
|
|
||||||
var fileInfo = new FileInfo(file);
|
var fileInfo = new FileInfo(file);
|
||||||
fileInfo.Exists.Should().BeTrue();
|
fileInfo.Exists.Should().BeTrue();
|
||||||
@@ -371,7 +372,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_download_file_with_redirect()
|
public void should_download_file_with_redirect()
|
||||||
{
|
{
|
||||||
var file = GetTempFilePath();
|
var file = GetTempFilePath();
|
||||||
|
|
||||||
@@ -379,7 +380,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();
|
||||||
|
|
||||||
await Subject.DownloadFileAsync(request.Url.FullUri, file);
|
Subject.DownloadFile(request.Url.FullUri, file);
|
||||||
|
|
||||||
ExceptionVerification.ExpectedErrors(0);
|
ExceptionVerification.ExpectedErrors(0);
|
||||||
|
|
||||||
@@ -393,7 +394,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
{
|
{
|
||||||
var file = GetTempFilePath();
|
var file = GetTempFilePath();
|
||||||
|
|
||||||
Assert.ThrowsAsync<HttpException>(async () => await Subject.DownloadFileAsync("https://download.sonarr.tv/wrongpath", file));
|
Assert.Throws<HttpException>(() => Subject.DownloadFile("https://download.sonarr.tv/wrongpath", file));
|
||||||
|
|
||||||
File.Exists(file).Should().BeFalse();
|
File.Exists(file).Should().BeFalse();
|
||||||
|
|
||||||
@@ -401,7 +402,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_not_write_redirect_content_to_stream()
|
public void should_not_write_redirect_content_to_stream()
|
||||||
{
|
{
|
||||||
var file = GetTempFilePath();
|
var file = GetTempFilePath();
|
||||||
|
|
||||||
@@ -411,7 +412,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
request.AllowAutoRedirect = false;
|
request.AllowAutoRedirect = false;
|
||||||
request.ResponseStream = fileStream;
|
request.ResponseStream = fileStream;
|
||||||
|
|
||||||
var response = await Subject.GetAsync(request);
|
var response = Subject.Get(request);
|
||||||
|
|
||||||
response.StatusCode.Should().Be(HttpStatusCode.Moved);
|
response.StatusCode.Should().Be(HttpStatusCode.Moved);
|
||||||
}
|
}
|
||||||
@@ -426,12 +427,12 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_send_cookie()
|
public void 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 = await Subject.GetAsync<HttpBinResource>(request);
|
var response = Subject.Get<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Headers.Should().ContainKey("Cookie");
|
response.Resource.Headers.Should().ContainKey("Cookie");
|
||||||
|
|
||||||
@@ -460,13 +461,13 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_preserve_cookie_during_session()
|
public void should_preserve_cookie_during_session()
|
||||||
{
|
{
|
||||||
GivenOldCookie();
|
GivenOldCookie();
|
||||||
|
|
||||||
var request = new HttpRequest($"https://{_httpBinHost2}/get");
|
var request = new HttpRequest($"https://{_httpBinHost2}/get");
|
||||||
|
|
||||||
var response = await Subject.GetAsync<HttpBinResource>(request);
|
var response = Subject.Get<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Headers.Should().ContainKey("Cookie");
|
response.Resource.Headers.Should().ContainKey("Cookie");
|
||||||
|
|
||||||
@@ -476,30 +477,30 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_not_send_cookie_to_other_host()
|
public void 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 = await Subject.GetAsync<HttpBinResource>(request);
|
var response = Subject.Get<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Headers.Should().NotContainKey("Cookie");
|
response.Resource.Headers.Should().NotContainKey("Cookie");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_not_store_request_cookie()
|
public void 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 = await Subject.GetAsync<HttpBinResource>(requestGet);
|
var responseGet = Subject.Get<HttpBinResource>(requestGet);
|
||||||
|
|
||||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||||
requestCookies.AllowAutoRedirect = false;
|
requestCookies.AllowAutoRedirect = false;
|
||||||
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().BeEmpty();
|
responseCookies.Resource.Cookies.Should().BeEmpty();
|
||||||
|
|
||||||
@@ -507,18 +508,18 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_store_request_cookie()
|
public void 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 = await Subject.GetAsync<HttpBinResource>(requestGet);
|
var responseGet = Subject.Get<HttpBinResource>(requestGet);
|
||||||
|
|
||||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||||
requestCookies.AllowAutoRedirect = false;
|
requestCookies.AllowAutoRedirect = false;
|
||||||
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||||
|
|
||||||
@@ -526,7 +527,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_delete_request_cookie()
|
public void 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");
|
||||||
@@ -535,13 +536,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 = await Subject.GetAsync<HttpCookieResource>(requestDelete);
|
var responseCookies = Subject.Get<HttpCookieResource>(requestDelete);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().BeEmpty();
|
responseCookies.Resource.Cookies.Should().BeEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_clear_request_cookie()
|
public void 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");
|
||||||
@@ -549,7 +550,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
requestSet.StoreRequestCookie = true;
|
requestSet.StoreRequestCookie = true;
|
||||||
requestSet.StoreResponseCookie = false;
|
requestSet.StoreResponseCookie = false;
|
||||||
|
|
||||||
var responseSet = await Subject.GetAsync<HttpCookieResource>(requestSet);
|
var responseSet = Subject.Get<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);
|
||||||
@@ -557,24 +558,24 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
requestClear.StoreRequestCookie = true;
|
requestClear.StoreRequestCookie = true;
|
||||||
requestClear.StoreResponseCookie = false;
|
requestClear.StoreResponseCookie = false;
|
||||||
|
|
||||||
var responseClear = await Subject.GetAsync<HttpCookieResource>(requestClear);
|
var responseClear = Subject.Get<HttpCookieResource>(requestClear);
|
||||||
|
|
||||||
responseClear.Resource.Cookies.Should().BeEmpty();
|
responseClear.Resource.Cookies.Should().BeEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_not_store_response_cookie()
|
public void 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 = await Subject.GetAsync(requestSet);
|
var responseSet = Subject.Get(requestSet);
|
||||||
|
|
||||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||||
|
|
||||||
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().BeEmpty();
|
responseCookies.Resource.Cookies.Should().BeEmpty();
|
||||||
|
|
||||||
@@ -582,18 +583,18 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_store_response_cookie()
|
public void 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 = await Subject.GetAsync(requestSet);
|
var responseSet = Subject.Get(requestSet);
|
||||||
|
|
||||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||||
|
|
||||||
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||||
|
|
||||||
@@ -601,13 +602,13 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_temp_store_response_cookie()
|
public void 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 = await Subject.GetAsync<HttpCookieResource>(requestSet);
|
var responseSet = Subject.Get<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");
|
||||||
@@ -616,7 +617,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_overwrite_response_cookie()
|
public void 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");
|
||||||
@@ -624,11 +625,11 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
requestSet.StoreRequestCookie = false;
|
requestSet.StoreRequestCookie = false;
|
||||||
requestSet.StoreResponseCookie = true;
|
requestSet.StoreResponseCookie = true;
|
||||||
|
|
||||||
var responseSet = await Subject.GetAsync(requestSet);
|
var responseSet = Subject.Get(requestSet);
|
||||||
|
|
||||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||||
|
|
||||||
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||||
|
|
||||||
@@ -636,7 +637,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_overwrite_temp_response_cookie()
|
public void 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");
|
||||||
@@ -644,13 +645,13 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
requestSet.StoreRequestCookie = true;
|
requestSet.StoreRequestCookie = true;
|
||||||
requestSet.StoreResponseCookie = false;
|
requestSet.StoreResponseCookie = false;
|
||||||
|
|
||||||
var responseSet = await Subject.GetAsync<HttpCookieResource>(requestSet);
|
var responseSet = Subject.Get<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 = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "oldcookie");
|
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "oldcookie");
|
||||||
|
|
||||||
@@ -658,14 +659,14 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_not_delete_response_cookie()
|
public void 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 = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||||
|
|
||||||
@@ -674,13 +675,13 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
requestDelete.StoreRequestCookie = false;
|
requestDelete.StoreRequestCookie = false;
|
||||||
requestDelete.StoreResponseCookie = false;
|
requestDelete.StoreResponseCookie = false;
|
||||||
|
|
||||||
var responseDelete = await Subject.GetAsync(requestDelete);
|
var responseDelete = Subject.Get(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 = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||||
|
|
||||||
@@ -688,14 +689,14 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_delete_response_cookie()
|
public void 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 = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||||
|
|
||||||
@@ -704,13 +705,13 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
requestDelete.StoreRequestCookie = false;
|
requestDelete.StoreRequestCookie = false;
|
||||||
requestDelete.StoreResponseCookie = true;
|
requestDelete.StoreResponseCookie = true;
|
||||||
|
|
||||||
var responseDelete = await Subject.GetAsync(requestDelete);
|
var responseDelete = Subject.Get(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 = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().BeEmpty();
|
responseCookies.Resource.Cookies.Should().BeEmpty();
|
||||||
|
|
||||||
@@ -718,14 +719,14 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_delete_temp_response_cookie()
|
public void 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 = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||||
|
|
||||||
@@ -733,7 +734,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 = await Subject.GetAsync<HttpCookieResource>(requestDelete);
|
var responseDelete = Subject.Get<HttpCookieResource>(requestDelete);
|
||||||
|
|
||||||
responseDelete.Resource.Cookies.Should().BeEmpty();
|
responseDelete.Resource.Cookies.Should().BeEmpty();
|
||||||
|
|
||||||
@@ -751,13 +752,13 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/status/429");
|
var request = new HttpRequest($"https://{_httpBinHost}/status/429");
|
||||||
|
|
||||||
Assert.ThrowsAsync<TooManyRequestsException>(async () => await Subject.GetAsync(request));
|
Assert.Throws<TooManyRequestsException>(() => Subject.Get(request));
|
||||||
|
|
||||||
ExceptionVerification.IgnoreWarns();
|
ExceptionVerification.IgnoreWarns();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_call_interceptor()
|
public void should_call_interceptor()
|
||||||
{
|
{
|
||||||
Mocker.SetConstant<IEnumerable<IHttpRequestInterceptor>>(new[] { Mocker.GetMock<IHttpRequestInterceptor>().Object });
|
Mocker.SetConstant<IEnumerable<IHttpRequestInterceptor>>(new[] { Mocker.GetMock<IHttpRequestInterceptor>().Object });
|
||||||
|
|
||||||
@@ -771,7 +772,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
|
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||||
|
|
||||||
await Subject.GetAsync(request);
|
Subject.Get(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());
|
||||||
@@ -782,7 +783,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
|
|
||||||
[TestCase("en-US")]
|
[TestCase("en-US")]
|
||||||
[TestCase("es-ES")]
|
[TestCase("es-ES")]
|
||||||
public async Task should_parse_malformed_cloudflare_cookie(string culture)
|
public void 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);
|
||||||
@@ -798,11 +799,11 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
requestSet.AllowAutoRedirect = false;
|
requestSet.AllowAutoRedirect = false;
|
||||||
requestSet.StoreResponseCookie = true;
|
requestSet.StoreResponseCookie = true;
|
||||||
|
|
||||||
var responseSet = await Subject.GetAsync(requestSet);
|
var responseSet = Subject.Get(requestSet);
|
||||||
|
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||||
|
|
||||||
var response = await Subject.GetAsync<HttpBinResource>(request);
|
var response = Subject.Get<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Headers.Should().ContainKey("Cookie");
|
response.Resource.Headers.Should().ContainKey("Cookie");
|
||||||
|
|
||||||
@@ -820,7 +821,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 async Task should_reject_malformed_domain_cookie(string malformedCookie)
|
public void should_reject_malformed_domain_cookie(string malformedCookie)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -830,11 +831,11 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
requestSet.AllowAutoRedirect = false;
|
requestSet.AllowAutoRedirect = false;
|
||||||
requestSet.StoreResponseCookie = true;
|
requestSet.StoreResponseCookie = true;
|
||||||
|
|
||||||
var responseSet = await Subject.GetAsync(requestSet);
|
var responseSet = Subject.Get(requestSet);
|
||||||
|
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||||
|
|
||||||
var response = await Subject.GetAsync<HttpBinResource>(request);
|
var response = Subject.Get<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Headers.Should().NotContainKey("Cookie");
|
response.Resource.Headers.Should().NotContainKey("Cookie");
|
||||||
|
|
||||||
@@ -846,12 +847,12 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_correctly_use_basic_auth()
|
public void should_correctly_use_basic_auth_with_basic_network_credential()
|
||||||
{
|
{
|
||||||
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 = await Subject.ExecuteAsync(request);
|
var response = Subject.Execute(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")]
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using ICSharpCode.SharpZipLib.Core;
|
using ICSharpCode.SharpZipLib.Core;
|
||||||
using ICSharpCode.SharpZipLib.GZip;
|
using ICSharpCode.SharpZipLib.GZip;
|
||||||
@@ -12,7 +11,7 @@ namespace NzbDrone.Common
|
|||||||
public interface IArchiveService
|
public interface IArchiveService
|
||||||
{
|
{
|
||||||
void Extract(string compressedFile, string destination);
|
void Extract(string compressedFile, string destination);
|
||||||
void CreateZip(string path, IEnumerable<string> files);
|
void CreateZip(string path, params string[] files);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ArchiveService : IArchiveService
|
public class ArchiveService : IArchiveService
|
||||||
@@ -40,7 +39,7 @@ namespace NzbDrone.Common
|
|||||||
_logger.Debug("Extraction complete.");
|
_logger.Debug("Extraction complete.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreateZip(string path, IEnumerable<string> files)
|
public void CreateZip(string path, params string[] files)
|
||||||
{
|
{
|
||||||
using (var zipFile = ZipFile.Create(path))
|
using (var zipFile = ZipFile.Create(path))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ namespace NzbDrone.Common.Disk
|
|||||||
{
|
{
|
||||||
CheckFolderExists(path);
|
CheckFolderExists(path);
|
||||||
|
|
||||||
var dirFiles = GetFiles(path, true).ToList();
|
var dirFiles = GetFiles(path, SearchOption.AllDirectories).ToList();
|
||||||
|
|
||||||
if (!dirFiles.Any())
|
if (!dirFiles.Any())
|
||||||
{
|
{
|
||||||
@@ -156,11 +156,11 @@ namespace NzbDrone.Common.Disk
|
|||||||
return _fileSystem.Directory.EnumerateFileSystemEntries(path).Empty();
|
return _fileSystem.Directory.EnumerateFileSystemEntries(path).Empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<string> GetDirectories(string path)
|
public string[] GetDirectories(string path)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
return _fileSystem.Directory.EnumerateDirectories(path);
|
return _fileSystem.Directory.GetDirectories(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string[] GetDirectories(string path, SearchOption searchOption)
|
public string[] GetDirectories(string path, SearchOption searchOption)
|
||||||
@@ -170,22 +170,18 @@ namespace NzbDrone.Common.Disk
|
|||||||
return _fileSystem.Directory.GetDirectories(path, "*", searchOption);
|
return _fileSystem.Directory.GetDirectories(path, "*", searchOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<string> GetFiles(string path, bool recursive)
|
public string[] GetFiles(string path, SearchOption searchOption)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
return _fileSystem.Directory.EnumerateFiles(path, "*", new EnumerationOptions
|
return _fileSystem.Directory.GetFiles(path, "*.*", searchOption);
|
||||||
{
|
|
||||||
RecurseSubdirectories = recursive,
|
|
||||||
IgnoreInaccessible = true
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long GetFolderSize(string path)
|
public long GetFolderSize(string path)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
return GetFiles(path, true).Sum(e => _fileSystem.FileInfo.FromFileName(e).Length);
|
return GetFiles(path, SearchOption.AllDirectories).Sum(e => _fileSystem.FileInfo.FromFileName(e).Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long GetFileSize(string path)
|
public long GetFileSize(string path)
|
||||||
@@ -306,9 +302,8 @@ namespace NzbDrone.Common.Disk
|
|||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
var files = GetFiles(path, recursive);
|
var files = _fileSystem.Directory.GetFiles(path, "*.*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
|
||||||
|
Array.ForEach(files, RemoveReadOnly);
|
||||||
files.ToList().ForEach(RemoveReadOnly);
|
|
||||||
|
|
||||||
_fileSystem.Directory.Delete(path, recursive);
|
_fileSystem.Directory.Delete(path, recursive);
|
||||||
}
|
}
|
||||||
@@ -409,7 +404,7 @@ namespace NzbDrone.Common.Disk
|
|||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
foreach (var file in GetFiles(path, false))
|
foreach (var file in GetFiles(path, SearchOption.TopDirectoryOnly))
|
||||||
{
|
{
|
||||||
DeleteFile(file);
|
DeleteFile(file);
|
||||||
}
|
}
|
||||||
@@ -509,17 +504,13 @@ namespace NzbDrone.Common.Disk
|
|||||||
return _fileSystem.DirectoryInfo.FromDirectoryName(path);
|
return _fileSystem.DirectoryInfo.FromDirectoryName(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<IFileInfo> GetFileInfos(string path, bool recursive = false)
|
public List<IFileInfo> GetFileInfos(string path, SearchOption searchOption = SearchOption.TopDirectoryOnly)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
var di = _fileSystem.DirectoryInfo.FromDirectoryName(path);
|
var di = _fileSystem.DirectoryInfo.FromDirectoryName(path);
|
||||||
|
|
||||||
return di.EnumerateFiles("*", new EnumerationOptions
|
return di.GetFiles("*", searchOption).ToList();
|
||||||
{
|
|
||||||
RecurseSubdirectories = recursive,
|
|
||||||
IgnoreInaccessible = true
|
|
||||||
}).ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IFileInfo GetFileInfo(string path)
|
public IFileInfo GetFileInfo(string path)
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ namespace NzbDrone.Common.Disk
|
|||||||
bool FileExists(string path, StringComparison stringComparison);
|
bool FileExists(string path, StringComparison stringComparison);
|
||||||
bool FolderWritable(string path);
|
bool FolderWritable(string path);
|
||||||
bool FolderEmpty(string path);
|
bool FolderEmpty(string path);
|
||||||
IEnumerable<string> GetDirectories(string path);
|
string[] GetDirectories(string path);
|
||||||
IEnumerable<string> GetFiles(string path, bool recursive);
|
string[] GetFiles(string path, SearchOption searchOption);
|
||||||
long GetFolderSize(string path);
|
long GetFolderSize(string path);
|
||||||
long GetFileSize(string path);
|
long GetFileSize(string path);
|
||||||
void CreateFolder(string path);
|
void CreateFolder(string path);
|
||||||
@@ -54,7 +54,7 @@ namespace NzbDrone.Common.Disk
|
|||||||
IDirectoryInfo GetDirectoryInfo(string path);
|
IDirectoryInfo GetDirectoryInfo(string path);
|
||||||
List<IDirectoryInfo> GetDirectoryInfos(string path);
|
List<IDirectoryInfo> GetDirectoryInfos(string path);
|
||||||
IFileInfo GetFileInfo(string path);
|
IFileInfo GetFileInfo(string path);
|
||||||
List<IFileInfo> GetFileInfos(string path, bool recursive = false);
|
List<IFileInfo> GetFileInfos(string path, SearchOption searchOption = SearchOption.TopDirectoryOnly);
|
||||||
void RemoveEmptySubfolders(string path);
|
void RemoveEmptySubfolders(string path);
|
||||||
void SaveStream(Stream stream, string path);
|
void SaveStream(Stream stream, string path);
|
||||||
bool IsValidFolderPermissionMask(string mask);
|
bool IsValidFolderPermissionMask(string mask);
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ namespace NzbDrone.Common.EnvironmentInfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (IsLinux &&
|
if (IsLinux &&
|
||||||
(File.Exists("/.dockerenv") ||
|
((File.Exists("/proc/1/cgroup") && File.ReadAllText("/proc/1/cgroup").Contains("/docker/")) ||
|
||||||
(File.Exists("/proc/1/cgroup") && File.ReadAllText("/proc/1/cgroup").Contains("/docker/"))))
|
(File.Exists("/proc/1/mountinfo") && File.ReadAllText("/proc/1/mountinfo").Contains("/docker/"))))
|
||||||
{
|
{
|
||||||
IsDocker = true;
|
IsDocker = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ namespace NzbDrone.Common.Extensions
|
|||||||
|
|
||||||
public static bool IsPathValid(this string path, PathValidationType validationType)
|
public static bool IsPathValid(this string path, PathValidationType validationType)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(path) || path.ContainsInvalidPathChars())
|
if (path.ContainsInvalidPathChars() || string.IsNullOrWhiteSpace(path))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -160,11 +160,6 @@ namespace NzbDrone.Common.Extensions
|
|||||||
|
|
||||||
public static bool ContainsInvalidPathChars(this string text)
|
public static bool ContainsInvalidPathChars(this string text)
|
||||||
{
|
{
|
||||||
if (text.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(text));
|
|
||||||
}
|
|
||||||
|
|
||||||
return text.IndexOfAny(Path.GetInvalidPathChars()) >= 0;
|
return text.IndexOfAny(Path.GetInvalidPathChars()) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
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
|
||||||
{
|
{
|
||||||
Task<HttpResponse> GetResponseAsync(HttpRequest request, CookieContainer cookies);
|
HttpResponse GetResponse(HttpRequest request, CookieContainer cookies);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,13 +44,9 @@ namespace NzbDrone.Common.Http.Dispatchers
|
|||||||
_credentialCache = cacheManager.GetCache<CredentialCache>(typeof(ManagedHttpDispatcher), "credentialcache");
|
_credentialCache = cacheManager.GetCache<CredentialCache>(typeof(ManagedHttpDispatcher), "credentialcache");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<HttpResponse> GetResponseAsync(HttpRequest request, CookieContainer cookies)
|
public HttpResponse GetResponse(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;
|
||||||
|
|
||||||
@@ -103,7 +99,7 @@ namespace NzbDrone.Common.Http.Dispatchers
|
|||||||
|
|
||||||
var httpClient = GetClient(request.Url);
|
var httpClient = GetClient(request.Url);
|
||||||
|
|
||||||
using var responseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cts.Token);
|
using var responseMessage = httpClient.Send(requestMessage, HttpCompletionOption.ResponseHeadersRead, cts.Token);
|
||||||
{
|
{
|
||||||
byte[] data = null;
|
byte[] data = null;
|
||||||
|
|
||||||
@@ -111,11 +107,11 @@ namespace NzbDrone.Common.Http.Dispatchers
|
|||||||
{
|
{
|
||||||
if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
|
if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
|
||||||
{
|
{
|
||||||
await responseMessage.Content.CopyToAsync(request.ResponseStream, null, cts.Token);
|
responseMessage.Content.CopyTo(request.ResponseStream, null, cts.Token);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
data = await responseMessage.Content.ReadAsByteArrayAsync(cts.Token);
|
data = responseMessage.Content.ReadAsByteArrayAsync(cts.Token).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -127,7 +123,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, responseMessage.Version);
|
return new HttpResponse(request, new HttpHeader(headers), data, responseMessage.StatusCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,8 +160,6 @@ namespace NzbDrone.Common.Http.Dispatchers
|
|||||||
|
|
||||||
var client = new System.Net.Http.HttpClient(handler)
|
var client = new System.Net.Http.HttpClient(handler)
|
||||||
{
|
{
|
||||||
DefaultRequestVersion = HttpVersion.Version20,
|
|
||||||
DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower,
|
|
||||||
Timeout = Timeout.InfiniteTimeSpan
|
Timeout = Timeout.InfiniteTimeSpan
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ 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;
|
||||||
@@ -26,16 +25,6 @@ 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
|
||||||
@@ -63,11 +52,11 @@ namespace NzbDrone.Common.Http
|
|||||||
_cookieContainerCache = cacheManager.GetCache<CookieContainer>(typeof(HttpClient));
|
_cookieContainerCache = cacheManager.GetCache<CookieContainer>(typeof(HttpClient));
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual async Task<HttpResponse> ExecuteAsync(HttpRequest request)
|
public HttpResponse Execute(HttpRequest request)
|
||||||
{
|
{
|
||||||
var cookieContainer = InitializeRequestCookies(request);
|
var cookieContainer = InitializeRequestCookies(request);
|
||||||
|
|
||||||
var response = await ExecuteRequestAsync(request, cookieContainer);
|
var response = ExecuteRequest(request, cookieContainer);
|
||||||
|
|
||||||
if (request.AllowAutoRedirect && response.HasHttpRedirect)
|
if (request.AllowAutoRedirect && response.HasHttpRedirect)
|
||||||
{
|
{
|
||||||
@@ -93,7 +82,7 @@ namespace NzbDrone.Common.Http
|
|||||||
request.ContentSummary = null;
|
request.ContentSummary = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
response = await ExecuteRequestAsync(request, cookieContainer);
|
response = ExecuteRequest(request, cookieContainer);
|
||||||
}
|
}
|
||||||
while (response.HasHttpRedirect);
|
while (response.HasHttpRedirect);
|
||||||
}
|
}
|
||||||
@@ -123,11 +112,6 @@ 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
|
||||||
@@ -138,7 +122,7 @@ namespace NzbDrone.Common.Http
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<HttpResponse> ExecuteRequestAsync(HttpRequest request, CookieContainer cookieContainer)
|
private HttpResponse ExecuteRequest(HttpRequest request, CookieContainer cookieContainer)
|
||||||
{
|
{
|
||||||
foreach (var interceptor in _requestInterceptors)
|
foreach (var interceptor in _requestInterceptors)
|
||||||
{
|
{
|
||||||
@@ -147,14 +131,14 @@ namespace NzbDrone.Common.Http
|
|||||||
|
|
||||||
if (request.RateLimit != TimeSpan.Zero)
|
if (request.RateLimit != TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
await _rateLimitService.WaitAndPulseAsync(request.Url.Host, request.RateLimitKey, request.RateLimit);
|
_rateLimitService.WaitAndPulse(request.Url.Host, request.RateLimitKey, request.RateLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Trace(request);
|
_logger.Trace(request);
|
||||||
|
|
||||||
var stopWatch = Stopwatch.StartNew();
|
var stopWatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
var response = await _httpDispatcher.GetResponseAsync(request, cookieContainer);
|
var response = _httpDispatcher.GetResponse(request, cookieContainer);
|
||||||
|
|
||||||
HandleResponseCookies(response, cookieContainer);
|
HandleResponseCookies(response, cookieContainer);
|
||||||
|
|
||||||
@@ -262,7 +246,7 @@ namespace NzbDrone.Common.Http
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DownloadFileAsync(string url, string fileName, string userAgent = null)
|
public void DownloadFile(string url, string fileName, string userAgent = null)
|
||||||
{
|
{
|
||||||
var fileNamePart = fileName + ".part";
|
var fileNamePart = fileName + ".part";
|
||||||
|
|
||||||
@@ -277,13 +261,12 @@ 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();
|
||||||
await using (var fileStream = new FileStream(fileNamePart, FileMode.Create, FileAccess.ReadWrite))
|
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;
|
||||||
request.RequestTimeout = TimeSpan.FromSeconds(300);
|
var response = Get(request);
|
||||||
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"))
|
||||||
{
|
{
|
||||||
@@ -310,71 +293,38 @@ namespace NzbDrone.Common.Http
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
return ExecuteAsync(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpResponse Get(HttpRequest request)
|
public HttpResponse Get(HttpRequest request)
|
||||||
{
|
{
|
||||||
return Task.Run(() => GetAsync(request)).GetAwaiter().GetResult();
|
request.Method = HttpMethod.Get;
|
||||||
}
|
return Execute(request);
|
||||||
|
|
||||||
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()
|
||||||
{
|
{
|
||||||
return Task.Run(() => GetAsync<T>(request)).GetAwaiter().GetResult();
|
var response = Get(request);
|
||||||
}
|
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)
|
||||||
{
|
{
|
||||||
return Task.Run(() => HeadAsync(request)).GetAwaiter().GetResult();
|
request.Method = HttpMethod.Head;
|
||||||
}
|
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)
|
||||||
{
|
{
|
||||||
return Task.Run(() => PostAsync(request)).GetAwaiter().GetResult();
|
request.Method = HttpMethod.Post;
|
||||||
}
|
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()
|
||||||
{
|
{
|
||||||
return Task.Run(() => PostAsync<T>(request)).GetAwaiter().GetResult();
|
var response = Post(request);
|
||||||
|
CheckResponseContentType(response);
|
||||||
|
return new HttpResponse<T>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckResponseContentType(HttpResponse response)
|
private void CheckResponseContentType(HttpResponse response)
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ namespace NzbDrone.Common.Http
|
|||||||
Method = HttpMethod.Get;
|
Method = HttpMethod.Get;
|
||||||
Url = new HttpUri(url);
|
Url = new HttpUri(url);
|
||||||
Headers = new HttpHeader();
|
Headers = new HttpHeader();
|
||||||
ConnectionKeepAlive = true;
|
|
||||||
AllowAutoRedirect = true;
|
AllowAutoRedirect = true;
|
||||||
StoreRequestCookie = true;
|
StoreRequestCookie = true;
|
||||||
LogHttpError = true;
|
LogHttpError = true;
|
||||||
|
|||||||
@@ -9,31 +9,28 @@ namespace NzbDrone.Common.Http
|
|||||||
{
|
{
|
||||||
public class HttpResponse
|
public class HttpResponse
|
||||||
{
|
{
|
||||||
private static readonly Regex RegexSetCookie = new ("^(.*?)=(.*?)(?:;|$)", RegexOptions.Compiled);
|
private static readonly Regex RegexSetCookie = new Regex("^(.*?)=(.*?)(?:;|$)", RegexOptions.Compiled);
|
||||||
|
|
||||||
public HttpResponse(HttpRequest request, HttpHeader headers, byte[] binaryData, HttpStatusCode statusCode = HttpStatusCode.OK, Version version = null)
|
public HttpResponse(HttpRequest request, HttpHeader headers, byte[] binaryData, HttpStatusCode statusCode = HttpStatusCode.OK)
|
||||||
{
|
{
|
||||||
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, Version version = null)
|
public HttpResponse(HttpRequest request, HttpHeader headers, string content, HttpStatusCode statusCode = HttpStatusCode.OK)
|
||||||
{
|
{
|
||||||
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;
|
||||||
@@ -87,7 +84,7 @@ namespace NzbDrone.Common.Http
|
|||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
var result = $"Res: HTTP/{Version} [{Request.Method}] {Request.Url}: {(int)StatusCode}.{StatusCode} ({ResponseData?.Length ?? 0} bytes)";
|
var result = string.Format("Res: [{0}] {1}: {2}.{3} ({4} bytes)", Request.Method, Request.Url, (int)StatusCode, StatusCode, ResponseData?.Length ?? 0);
|
||||||
|
|
||||||
if (HasHttpError && Headers.ContentType.IsNotNullOrWhiteSpace() && !Headers.ContentType.Equals("text/html", StringComparison.InvariantCultureIgnoreCase))
|
if (HasHttpError && Headers.ContentType.IsNotNullOrWhiteSpace() && !Headers.ContentType.Equals("text/html", StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
@@ -102,7 +99,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, response.Version)
|
: base(response.Request, response.Headers, response.ResponseData, response.StatusCode)
|
||||||
{
|
{
|
||||||
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", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
new (@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
|
||||||
|
|
||||||
// Path
|
// Path
|
||||||
new (@"C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
new (@"C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
|
|||||||
@@ -131,9 +131,9 @@ namespace NzbDrone.Common.Instrumentation
|
|||||||
|
|
||||||
private static void RegisterAppFile(IAppFolderInfo appFolderInfo)
|
private static void RegisterAppFile(IAppFolderInfo appFolderInfo)
|
||||||
{
|
{
|
||||||
RegisterAppFile(appFolderInfo, "appFileInfo", "readarr.txt", 5, LogLevel.Info);
|
RegisterAppFile(appFolderInfo, "appFileInfo", "Readarr.txt", 5, LogLevel.Info);
|
||||||
RegisterAppFile(appFolderInfo, "appFileDebug", "readarr.debug.txt", 50, LogLevel.Off);
|
RegisterAppFile(appFolderInfo, "appFileDebug", "Readarr.debug.txt", 50, LogLevel.Off);
|
||||||
RegisterAppFile(appFolderInfo, "appFileTrace", "readarr.trace.txt", 50, LogLevel.Off);
|
RegisterAppFile(appFolderInfo, "appFileTrace", "Readarr.trace.txt", 50, LogLevel.Off);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RegisterAppFile(IAppFolderInfo appFolderInfo, string name, string fileName, int maxArchiveFiles, LogLevel minLogLevel)
|
private static void RegisterAppFile(IAppFolderInfo appFolderInfo, string name, string fileName, int maxArchiveFiles, LogLevel minLogLevel)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
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;
|
||||||
@@ -11,8 +10,6 @@ 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
|
||||||
@@ -31,34 +28,7 @@ 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);
|
||||||
|
|
||||||
@@ -89,7 +59,13 @@ namespace NzbDrone.Common.TPL
|
|||||||
|
|
||||||
waitUntil -= interval;
|
waitUntil -= interval;
|
||||||
|
|
||||||
return waitUntil - DateTime.UtcNow;
|
var delay = 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-28
@@ -1,10 +1,9 @@
|
|||||||
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;
|
||||||
@@ -34,13 +33,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
|
|||||||
};
|
};
|
||||||
|
|
||||||
Mocker
|
Mocker
|
||||||
.GetMock<IIndexerFactory>()
|
.GetMock<IIndexerRepository>()
|
||||||
.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>();
|
||||||
@@ -112,25 +106,5 @@ 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,10 +31,6 @@ namespace NzbDrone.Core.Test.DiskSpace
|
|||||||
.Setup(x => x.All())
|
.Setup(x => x.All())
|
||||||
.Returns(new List<RootFolder>() { _rootDir });
|
.Returns(new List<RootFolder>() { _rootDir });
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
|
||||||
.Setup(v => v.FolderExists(_rootDir.Path))
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(v => v.GetMounts())
|
.Setup(v => v.GetMounts())
|
||||||
.Returns(new List<IMount>());
|
.Returns(new List<IMount>());
|
||||||
|
|||||||
+26
-37
@@ -1,6 +1,5 @@
|
|||||||
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;
|
||||||
@@ -59,7 +58,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_download_report_if_book_was_not_already_downloaded()
|
public void 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));
|
||||||
@@ -67,12 +66,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));
|
||||||
|
|
||||||
await Subject.ProcessDecisions(decisions);
|
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 async Task should_only_download_book_once()
|
public void 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));
|
||||||
@@ -81,12 +80,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));
|
||||||
|
|
||||||
await Subject.ProcessDecisions(decisions);
|
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 async Task should_not_download_if_any_book_was_already_downloaded()
|
public void 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) },
|
||||||
@@ -100,12 +99,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));
|
||||||
|
|
||||||
await Subject.ProcessDecisions(decisions);
|
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 async Task should_return_downloaded_reports()
|
public void 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));
|
||||||
@@ -113,13 +112,11 @@ 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));
|
||||||
|
|
||||||
var result = await Subject.ProcessDecisions(decisions);
|
Subject.ProcessDecisions(decisions).Grabbed.Should().HaveCount(1);
|
||||||
|
|
||||||
result.Grabbed.Should().HaveCount(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_return_all_downloaded_reports()
|
public void should_return_all_downloaded_reports()
|
||||||
{
|
{
|
||||||
var remoteBook1 = GetRemoteBook(
|
var remoteBook1 = GetRemoteBook(
|
||||||
new List<Book> { GetBook(1) },
|
new List<Book> { GetBook(1) },
|
||||||
@@ -133,13 +130,11 @@ 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));
|
||||||
|
|
||||||
var result = await Subject.ProcessDecisions(decisions);
|
Subject.ProcessDecisions(decisions).Grabbed.Should().HaveCount(2);
|
||||||
|
|
||||||
result.Grabbed.Should().HaveCount(2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_only_return_downloaded_reports()
|
public void should_only_return_downloaded_reports()
|
||||||
{
|
{
|
||||||
var remoteBook1 = GetRemoteBook(
|
var remoteBook1 = GetRemoteBook(
|
||||||
new List<Book> { GetBook(1) },
|
new List<Book> { GetBook(1) },
|
||||||
@@ -158,13 +153,11 @@ 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));
|
||||||
|
|
||||||
var result = await Subject.ProcessDecisions(decisions);
|
Subject.ProcessDecisions(decisions).Grabbed.Should().HaveCount(2);
|
||||||
|
|
||||||
result.Grabbed.Should().HaveCount(2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_not_add_to_downloaded_list_when_download_fails()
|
public void 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));
|
||||||
@@ -173,11 +166,7 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +181,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_not_grab_if_pending()
|
public void 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));
|
||||||
@@ -200,12 +189,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)));
|
||||||
|
|
||||||
await Subject.ProcessDecisions(decisions);
|
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 async Task should_not_add_to_pending_if_book_was_grabbed()
|
public void 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));
|
||||||
@@ -214,12 +203,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)));
|
||||||
|
|
||||||
await Subject.ProcessDecisions(decisions);
|
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 async Task should_add_to_pending_even_if_already_added_to_pending()
|
public void 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));
|
||||||
@@ -228,12 +217,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)));
|
||||||
|
|
||||||
await Subject.ProcessDecisions(decisions);
|
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 async Task should_add_to_failed_if_already_failed_for_that_protocol()
|
public void 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));
|
||||||
@@ -245,12 +234,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"));
|
||||||
|
|
||||||
await Subject.ProcessDecisions(decisions);
|
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 async Task should_not_add_to_failed_if_failed_for_a_different_protocol()
|
public void 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);
|
||||||
@@ -263,13 +252,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"));
|
||||||
|
|
||||||
await Subject.ProcessDecisions(decisions);
|
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 async Task should_add_to_rejected_if_release_unavailable_on_indexer()
|
public void 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));
|
||||||
@@ -281,7 +270,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 = await Subject.ProcessDecisions(decisions);
|
var result = Subject.ProcessDecisions(decisions);
|
||||||
|
|
||||||
result.Grabbed.Should().BeEmpty();
|
result.Grabbed.Should().BeEmpty();
|
||||||
result.Rejected.Should().NotBeEmpty();
|
result.Rejected.Should().NotBeEmpty();
|
||||||
|
|||||||
+1
-1
@@ -30,7 +30,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
.Returns(new[] { targetDir });
|
.Returns(new[] { targetDir });
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(c => c.GetFiles(targetDir, true))
|
.Setup(c => c.GetFiles(targetDir, SearchOption.AllDirectories))
|
||||||
.Returns(new[] { Path.Combine(targetDir, "somefile.flac") });
|
.Returns(new[] { Path.Combine(targetDir, "somefile.flac") });
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
|
|||||||
+28
-31
@@ -4,7 +4,6 @@ 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;
|
||||||
@@ -70,7 +69,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
protected void GivenFailedDownload()
|
protected void GivenFailedDownload()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IHttpClient>()
|
Mocker.GetMock<IHttpClient>()
|
||||||
.Setup(s => s.GetAsync(It.IsAny<HttpRequest>()))
|
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
||||||
.Throws(new WebException());
|
.Throws(new WebException());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +82,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
.Returns(new[] { targetDir });
|
.Returns(new[] { targetDir });
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(c => c.GetFiles(targetDir, true))
|
.Setup(c => c.GetFiles(targetDir, SearchOption.AllDirectories))
|
||||||
.Returns(new[] { Path.Combine(targetDir, "somefile.flac") });
|
.Returns(new[] { Path.Combine(targetDir, "somefile.flac") });
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
@@ -148,19 +147,19 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Download_should_download_file_if_it_doesnt_exist()
|
public void Download_should_download_file_if_it_doesnt_exist()
|
||||||
{
|
{
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
await Subject.Download(remoteBook, CreateIndexer());
|
Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(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.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Download_should_save_magnet_if_enabled()
|
public void Download_should_save_magnet_if_enabled()
|
||||||
{
|
{
|
||||||
GivenMagnetFilePath();
|
GivenMagnetFilePath();
|
||||||
Subject.Definition.Settings.As<TorrentBlackholeSettings>().SaveMagnetFiles = true;
|
Subject.Definition.Settings.As<TorrentBlackholeSettings>().SaveMagnetFiles = true;
|
||||||
@@ -168,16 +167,16 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
remoteBook.Release.DownloadUrl = null;
|
remoteBook.Release.DownloadUrl = null;
|
||||||
|
|
||||||
await Subject.Download(remoteBook, CreateIndexer());
|
Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(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.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Download_should_save_magnet_using_specified_extension()
|
public void Download_should_save_magnet_using_specified_extension()
|
||||||
{
|
{
|
||||||
var magnetFileExtension = ".url";
|
var magnetFileExtension = ".url";
|
||||||
GivenMagnetFilePath(magnetFileExtension);
|
GivenMagnetFilePath(magnetFileExtension);
|
||||||
@@ -188,12 +187,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
remoteBook.Release.DownloadUrl = null;
|
remoteBook.Release.DownloadUrl = null;
|
||||||
|
|
||||||
await Subject.Download(remoteBook, CreateIndexer());
|
Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(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.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -203,31 +202,31 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
remoteBook.Release.DownloadUrl = null;
|
remoteBook.Release.DownloadUrl = null;
|
||||||
|
|
||||||
Assert.ThrowsAsync<ReleaseDownloadException>(async () => await Subject.Download(remoteBook, CreateIndexer()));
|
Assert.Throws<ReleaseDownloadException>(() => Subject.Download(remoteBook, CreateIndexer()));
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(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.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Download_should_prefer_torrent_over_magnet()
|
public void 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();
|
||||||
|
|
||||||
await Subject.Download(remoteBook, CreateIndexer());
|
Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(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.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Download_should_replace_illegal_characters_in_title()
|
public void 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));
|
||||||
@@ -235,11 +234,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
remoteBook.Release.Title = illegalTitle;
|
remoteBook.Release.Title = illegalTitle;
|
||||||
|
|
||||||
await Subject.Download(remoteBook, CreateIndexer());
|
Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(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.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -248,7 +247,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
remoteBook.Release.DownloadUrl = null;
|
remoteBook.Release.DownloadUrl = null;
|
||||||
|
|
||||||
Assert.ThrowsAsync<ReleaseDownloadException>(async () => await Subject.Download(remoteBook, CreateIndexer()));
|
Assert.Throws<ReleaseDownloadException>(() => Subject.Download(remoteBook, CreateIndexer()));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -318,13 +317,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_return_null_hash()
|
public void should_return_null_hash()
|
||||||
{
|
{
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var result = await Subject.Download(remoteBook, CreateIndexer());
|
Subject.Download(remoteBook, CreateIndexer()).Should().BeNull();
|
||||||
|
|
||||||
result.Should().BeNull();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-10
@@ -4,7 +4,6 @@ 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;
|
||||||
@@ -75,7 +74,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
.Returns(new[] { targetDir });
|
.Returns(new[] { targetDir });
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(c => c.GetFiles(targetDir, true))
|
.Setup(c => c.GetFiles(targetDir, SearchOption.AllDirectories))
|
||||||
.Returns(new[] { Path.Combine(targetDir, "somefile.flac") });
|
.Returns(new[] { Path.Combine(targetDir, "somefile.flac") });
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
@@ -120,19 +119,19 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Download_should_download_file_if_it_doesnt_exist()
|
public void Download_should_download_file_if_it_doesnt_exist()
|
||||||
{
|
{
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
await Subject.Download(remoteBook, CreateIndexer());
|
Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(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.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Download_should_replace_illegal_characters_in_title()
|
public void 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));
|
||||||
@@ -140,11 +139,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
|||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
remoteBook.Release.Title = illegalTitle;
|
remoteBook.Release.Title = illegalTitle;
|
||||||
|
|
||||||
await Subject.Download(remoteBook, CreateIndexer());
|
Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(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.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
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;
|
||||||
@@ -201,26 +200,26 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Download_should_return_unique_id()
|
public void Download_should_return_unique_id()
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
var id = 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 async Task Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
public void 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 = await Subject.Download(remoteBook, CreateIndexer());
|
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().Be(expectedHash);
|
id.Should().Be(expectedHash);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
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;
|
||||||
@@ -37,8 +36,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
|
|||||||
.Returns(() => CreateRemoteBook());
|
.Returns(() => CreateRemoteBook());
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>()
|
Mocker.GetMock<IHttpClient>()
|
||||||
.Setup(s => s.GetAsync(It.IsAny<HttpRequest>()))
|
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
||||||
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), Array.Empty<byte>())));
|
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[0]));
|
||||||
|
|
||||||
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>()))
|
||||||
|
|||||||
+7
-8
@@ -1,7 +1,6 @@
|
|||||||
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;
|
||||||
@@ -386,7 +385,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Download_with_TvDirectory_should_force_directory()
|
public void Download_with_TvDirectory_should_force_directory()
|
||||||
{
|
{
|
||||||
GivenSerialNumber();
|
GivenSerialNumber();
|
||||||
GivenTvDirectory();
|
GivenTvDirectory();
|
||||||
@@ -394,7 +393,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
@@ -403,7 +402,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Download_with_category_should_force_directory()
|
public void Download_with_category_should_force_directory()
|
||||||
{
|
{
|
||||||
GivenSerialNumber();
|
GivenSerialNumber();
|
||||||
GivenMusicCategory();
|
GivenMusicCategory();
|
||||||
@@ -411,7 +410,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
@@ -420,14 +419,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Download_without_TvDirectory_and_Category_should_use_default()
|
public void Download_without_TvDirectory_and_Category_should_use_default()
|
||||||
{
|
{
|
||||||
GivenSerialNumber();
|
GivenSerialNumber();
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
@@ -506,7 +505,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.ThrowsAsync(Is.InstanceOf<Exception>(), async () => await Subject.Download(remoteBook, CreateIndexer()));
|
Assert.Throws(Is.InstanceOf<Exception>(), () => 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());
|
||||||
|
|||||||
+7
-8
@@ -1,7 +1,6 @@
|
|||||||
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;
|
||||||
@@ -263,7 +262,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Download_with_TvDirectory_should_force_directory()
|
public void Download_with_TvDirectory_should_force_directory()
|
||||||
{
|
{
|
||||||
GivenSerialNumber();
|
GivenSerialNumber();
|
||||||
GivenTvDirectory();
|
GivenTvDirectory();
|
||||||
@@ -271,7 +270,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
@@ -280,7 +279,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Download_with_category_should_force_directory()
|
public void Download_with_category_should_force_directory()
|
||||||
{
|
{
|
||||||
GivenSerialNumber();
|
GivenSerialNumber();
|
||||||
GivenMusicCategory();
|
GivenMusicCategory();
|
||||||
@@ -288,7 +287,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
@@ -297,14 +296,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Download_without_TvDirectory_and_Category_should_use_default()
|
public void Download_without_TvDirectory_and_Category_should_use_default()
|
||||||
{
|
{
|
||||||
GivenSerialNumber();
|
GivenSerialNumber();
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
@@ -383,7 +382,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.ThrowsAsync(Is.InstanceOf<Exception>(), async () => await Subject.Download(remoteBook, CreateIndexer()));
|
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.Download(remoteBook, CreateIndexer()));
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadStationTaskProxy>()
|
Mocker.GetMock<IDownloadStationTaskProxy>()
|
||||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, _settings), Times.Never());
|
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, _settings), Times.Never());
|
||||||
|
|||||||
+8
-9
@@ -1,7 +1,6 @@
|
|||||||
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;
|
||||||
@@ -104,8 +103,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.HadoukenTests
|
|||||||
protected void GivenSuccessfulDownload()
|
protected void GivenSuccessfulDownload()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IHttpClient>()
|
Mocker.GetMock<IHttpClient>()
|
||||||
.Setup(s => s.GetAsync(It.IsAny<HttpRequest>()))
|
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
||||||
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), new byte[1000])));
|
.Returns<HttpRequest>(r => 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>()))
|
||||||
@@ -197,13 +196,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.HadoukenTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Download_should_return_unique_id()
|
public void Download_should_return_unique_id()
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
}
|
}
|
||||||
@@ -278,7 +277,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.HadoukenTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Download_from_magnet_link_should_return_hash_uppercase()
|
public void Download_from_magnet_link_should_return_hash_uppercase()
|
||||||
{
|
{
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
@@ -287,13 +286,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 = await Subject.Download(remoteBook, CreateIndexer());
|
var result = Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
Assert.IsFalse(result.Any(c => char.IsLower(c)));
|
Assert.IsFalse(result.Any(c => char.IsLower(c)));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Download_from_torrent_file_should_return_hash_uppercase()
|
public void Download_from_torrent_file_should_return_hash_uppercase()
|
||||||
{
|
{
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
@@ -301,7 +300,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 = await Subject.Download(remoteBook, CreateIndexer());
|
var result = Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
Assert.IsFalse(result.Any(c => char.IsLower(c)));
|
Assert.IsFalse(result.Any(c => char.IsLower(c)));
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-4
@@ -1,7 +1,6 @@
|
|||||||
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;
|
||||||
@@ -201,13 +200,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbVortexTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Download_should_return_unique_id()
|
public void Download_should_return_unique_id()
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
}
|
}
|
||||||
@@ -219,7 +218,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbVortexTests
|
|||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
Assert.ThrowsAsync<DownloadClientException>(async () => await Subject.Download(remoteBook, CreateIndexer()));
|
Assert.Throws<DownloadClientException>(() => Subject.Download(remoteBook, CreateIndexer()));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
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;
|
||||||
@@ -340,13 +339,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Download_should_return_unique_id()
|
public void Download_should_return_unique_id()
|
||||||
{
|
{
|
||||||
GivenSuccessfulDownload();
|
GivenSuccessfulDownload();
|
||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||||
|
|
||||||
id.Should().NotBeNullOrEmpty();
|
id.Should().NotBeNullOrEmpty();
|
||||||
}
|
}
|
||||||
@@ -358,7 +357,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
|
|||||||
|
|
||||||
var remoteBook = CreateRemoteBook();
|
var remoteBook = CreateRemoteBook();
|
||||||
|
|
||||||
Assert.ThrowsAsync<DownloadClientRejectedReleaseException>(async () => await Subject.Download(remoteBook, CreateIndexer()));
|
Assert.Throws<DownloadClientRejectedReleaseException>(() => Subject.Download(remoteBook, CreateIndexer()));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
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;
|
||||||
@@ -66,15 +65,15 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
|
|||||||
|
|
||||||
private void WithFailedDownload()
|
private void WithFailedDownload()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IHttpClient>().Setup(c => c.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Throws(new WebException());
|
Mocker.GetMock<IHttpClient>().Setup(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Throws(new WebException());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_download_file_if_it_doesnt_exist()
|
public void should_download_file_if_it_doesnt_exist()
|
||||||
{
|
{
|
||||||
await Subject.Download(_remoteBook, _indexer);
|
Subject.Download(_remoteBook, _indexer);
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(_nzbUrl, _nzbPath, null), Times.Once());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(_nzbUrl, _nzbPath, null), Times.Once());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -82,7 +81,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
|
|||||||
{
|
{
|
||||||
WithFailedDownload();
|
WithFailedDownload();
|
||||||
|
|
||||||
Assert.ThrowsAsync<WebException>(async () => await Subject.Download(_remoteBook, _indexer));
|
Assert.Throws<WebException>(() => Subject.Download(_remoteBook, _indexer));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -91,7 +90,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.ThrowsAsync<NotSupportedException>(async () => await Subject.Download(_remoteBook, _indexer));
|
Assert.Throws<NotSupportedException>(() => Subject.Download(_remoteBook, _indexer));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -101,15 +100,15 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task should_replace_illegal_characters_in_title()
|
public void 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;
|
||||||
|
|
||||||
await Subject.Download(_remoteBook, _indexer);
|
Subject.Download(_remoteBook, _indexer);
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(It.IsAny<string>(), expectedFilename, null), Times.Once());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), expectedFilename, null), Times.Once());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user