mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-06 13:31:28 -05:00
Compare commits
1 Commits
slack-refa
...
native-the
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
315c2e1b88 |
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,4 +1,5 @@
|
||||
name: Bug Report
|
||||
title: "[BUG]: "
|
||||
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']
|
||||
body:
|
||||
@@ -63,11 +64,12 @@ body:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Trace Logs?
|
||||
label: Anything else?
|
||||
description: |
|
||||
Trace Logs (https://wiki.servarr.com/radarr/troubleshooting#logging-and-log-files)
|
||||
Links? References? Anything that will give us more context about the issue you are encountering!
|
||||
***Generally speaking, all bug reports must have trace logs provided.***
|
||||
|
||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||
Additionally, any additional info? Screenshots? References? Anything that will give us more context about the issue you are encountering!
|
||||
validations:
|
||||
required: true
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,4 +1,5 @@
|
||||
name: Feature Request
|
||||
title: "[FEAT]: "
|
||||
description: 'Suggest an idea for Radarr'
|
||||
labels: ['Type: Feature Request', 'Status: Needs Triage']
|
||||
body:
|
||||
|
||||
45
README.md
45
README.md
@@ -1,21 +1,19 @@
|
||||
# Radarr
|
||||
|
||||
[](https://dev.azure.com/Radarr/Radarr/_build/latest?definitionId=1&branchName=develop)
|
||||
[](https://translate.servarr.com/engage/radarr/?utm_source=widget)
|
||||
[](https://translate.servarr.com/engage/radarr/?utm_source=widget)
|
||||
[](https://wiki.servarr.com/radarr/installation#docker)
|
||||

|
||||
[](#backers)
|
||||
[](#backers)
|
||||
[](#sponsors)
|
||||
[](#mega-sponsors)
|
||||
|
||||
Radarr is a movie collection manager for Usenet and BitTorrent users. It can monitor multiple RSS feeds for new movies and will interface with clients and indexers to grab, sort, and rename them. It can also be configured to automatically upgrade the quality of existing files in the library when a better quality format becomes available.
|
||||
Note that only one type of a given movie is supported. If you want both an 4k version and 1080p version of a given movie you will need multiple instances.
|
||||
|
||||
## Major Features Include
|
||||
## Major Features Include:
|
||||
|
||||
* Adding new movies with lots of information, such as trailers, ratings, etc.
|
||||
* Support for major platforms: Windows, Linux, macOS, Raspberry Pi, etc.
|
||||
* Can watch for better quality of the movies you have and do an automatic upgrade. *e.g. from DVD to Blu-Ray*
|
||||
* Can watch for better quality of the movies you have and do an automatic upgrade. *eg. from DVD to Blu-Ray*
|
||||
* Automatic failed download handling will try another release if one fails
|
||||
* Manual search so you can pick any release or to see why a release was not downloaded automatically
|
||||
* Full integration with SABnzbd and NZBGet
|
||||
@@ -23,60 +21,53 @@ Note that only one type of a given movie is supported. If you want both an 4k ve
|
||||
* Automatically importing downloaded movies
|
||||
* Recognizing Special Editions, Director's Cut, etc.
|
||||
* Identifying releases with hardcoded subs
|
||||
* Identifying releases with AKA movie names
|
||||
* SABnzbd, NZBGet, QBittorrent, Deluge, rTorrent, Transmission, uTorrent, and other download clients are supported and integrated
|
||||
* Full integration with Kodi and Plex (notifications, library updates)
|
||||
* QBittorrent, Deluge, rTorrent, Transmission, uTorrent, and other download clients are supported
|
||||
* Full integration with Kodi, Plex (notification, library update)
|
||||
* A beautiful UI
|
||||
* Importing Metadata such as trailers or subtitles
|
||||
* Adding metadata such as posters and information for Kodi and others to use
|
||||
* Advanced customization for profiles, such that Radarr will always download the copy you want
|
||||
* A beautiful UI
|
||||
|
||||
## Support
|
||||
|
||||
[](https://wiki.servarr.com/radarr)
|
||||
[](https://radarr.video/discord)
|
||||
[](https://www.reddit.com/r/Radarr)
|
||||
|
||||
Note: GitHub Issues are for Bugs and Feature Requests Only
|
||||
|
||||
[](https://radarr.video/discord)
|
||||
[](https://www.reddit.com/r/Radarr)
|
||||
[](https://github.com/Radarr/Radarr/issues)
|
||||
[](https://wiki.servarr.com/radarr)
|
||||
|
||||
## Contributors & Developers
|
||||
|
||||
[API Documentation](https://radarr.video/docs/api/)
|
||||
|
||||
This project exists thanks to all the people who contribute.
|
||||
- [Contribute (GitHub)](CONTRIBUTING.md)
|
||||
- [Contribution (Wiki Article)](https://wiki.servarr.com/radarr/contributing)
|
||||
This project exists thanks to all the people who contribute. [Contribute](CONTRIBUTING.md).
|
||||
<a href="https://github.com/Radarr/Radarr/graphs/contributors"><img src="https://opencollective.com/Radarr/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
[](https://github.com/Radarr/Radarr/graphs/contributors)
|
||||
|
||||
## Backers
|
||||
|
||||
Thank you to all our backers! 🙏 [Become a backer](https://opencollective.com/Radarr#backer)
|
||||
|
||||
[](https://opencollective.com/Radarr#backer)
|
||||
<img src="https://opencollective.com/Radarr/backers.svg?width=890"></a>
|
||||
|
||||
## Sponsors
|
||||
|
||||
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor](https://opencollective.com/Radarr#sponsor)
|
||||
|
||||
[](https://opencollective.com/Radarr#sponsor)
|
||||
<img src="https://opencollective.com/Radarr/sponsors.svg?width=890"></a>
|
||||
|
||||
## Mega Sponsors
|
||||
|
||||
[](https://opencollective.com/Radarr#mega-sponsor)
|
||||
<img src="https://opencollective.com/Radarr/tiers/mega-sponsor.svg?width=890"></a>
|
||||
|
||||
## JetBrains
|
||||
|
||||
Thank you to [<img src="/Logo/jetbrains.svg" alt="JetBrains" width="32"> JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools.
|
||||
|
||||
* [<img src="/Logo/resharper.svg" alt="ReSharper" width="32"> ReSharper](http://www.jetbrains.com/resharper/)
|
||||
* [<img src="/Logo/webstorm.svg" alt="WebStorm" width="32"> WebStorm](http://www.jetbrains.com/webstorm/)
|
||||
* [<img src="/Logo/rider.svg" alt="Rider" width="32"> Rider](http://www.jetbrains.com/rider/)
|
||||
* [<img src="/Logo/webstorm.svg" alt="WebStorm" width="32"> WebStorm](http://www.jetbrains.com/webstorm/)
|
||||
* [<img src="/Logo/rider.svg" alt="Rider" width="32"> Rider](http://www.jetbrains.com/rider/)
|
||||
* [<img src="/Logo/dottrace.svg" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/)
|
||||
|
||||
### License
|
||||
|
||||
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
|
||||
* Copyright 2010-2022
|
||||
* Copyright 2010-2021
|
||||
|
||||
@@ -13,7 +13,7 @@ variables:
|
||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||
sentryOrg: 'servarr'
|
||||
sentryUrl: 'https://sentry.servarr.com'
|
||||
dotnetVersion: '6.0.100'
|
||||
dotnetVersion: '5.0.401'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
|
||||
trigger:
|
||||
@@ -111,23 +111,23 @@ stages:
|
||||
artifact: '$(osName)Backend'
|
||||
displayName: Publish Backend
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net6.0/win-x64/publish'
|
||||
- publish: '$(testsFolder)/net5.0/win-x64/publish'
|
||||
artifact: WindowsCoreTests
|
||||
displayName: Publish Windows Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
|
||||
- publish: '$(testsFolder)/net5.0/linux-x64/publish'
|
||||
artifact: LinuxCoreTests
|
||||
displayName: Publish Linux Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
|
||||
- publish: '$(testsFolder)/net5.0/linux-musl-x64/publish'
|
||||
artifact: LinuxMuslCoreTests
|
||||
displayName: Publish Linux Musl Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
|
||||
- publish: '$(testsFolder)/net5.0/freebsd-x64/publish'
|
||||
artifact: FreebsdCoreTests
|
||||
displayName: Publish FreeBSD Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
|
||||
- publish: '$(testsFolder)/net5.0/osx-x64/publish'
|
||||
artifact: MacCoreTests
|
||||
displayName: Publish MacOS Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
@@ -203,12 +203,12 @@ stages:
|
||||
- bash: ./build.sh --packages
|
||||
displayName: Create Packages
|
||||
- bash: |
|
||||
setup/inno/ISCC.exe setup/radarr.iss //DFramework=net6.0 //DRuntime=win-x86
|
||||
cp setup/output/Radarr.*windows.net6.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x86-installer.exe
|
||||
setup/inno/ISCC.exe setup/radarr.iss //DFramework=net5.0 //DRuntime=win-x86
|
||||
cp setup/output/Radarr.*windows.net5.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x86-installer.exe
|
||||
displayName: Create .NET Core Windows installer
|
||||
- bash: |
|
||||
setup/inno/ISCC.exe setup/radarr.iss //DFramework=net6.0 //DRuntime=win-x64
|
||||
cp setup/output/Radarr.*windows.net6.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x64-installer.exe
|
||||
setup/inno/ISCC.exe setup/radarr.iss //DFramework=net5.0 //DRuntime=win-x64
|
||||
cp setup/output/Radarr.*windows.net5.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x64-installer.exe
|
||||
displayName: Create .NET Core Windows installer
|
||||
- publish: $(Build.ArtifactStagingDirectory)
|
||||
artifact: 'WindowsInstaller'
|
||||
@@ -251,44 +251,29 @@ stages:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x64.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/win-x64/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/win-x64/net5.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create Windows x86 Core zip
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x86.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/win-x86/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/win-x86/net5.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create MacOS x64 Core app
|
||||
displayName: Create MacOS Core app
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-x64.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/macos-app/net5.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create MacOS x64 Core tar
|
||||
displayName: Create MacOS Core tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-core-x64.tar.gz'
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-x64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create MacOS arm64 Core app
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-arm64.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create MacOS arm64 Core tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-core-arm64.tar.gz'
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-arm64/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/macos/net5.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create Linux Core tar
|
||||
inputs:
|
||||
@@ -296,7 +281,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-x64/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-x64/net5.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create Linux Musl Core tar
|
||||
inputs:
|
||||
@@ -304,7 +289,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net5.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create ARM32 Linux Core tar
|
||||
inputs:
|
||||
@@ -312,15 +297,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-arm/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create ARM32 Linux Musl Core tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-musl-core-arm.tar.gz'
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-arm/net5.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create ARM64 Linux Core tar
|
||||
inputs:
|
||||
@@ -328,7 +305,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net5.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create ARM64 Linux Musl Core tar
|
||||
inputs:
|
||||
@@ -336,7 +313,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net5.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create FreeBSD Core Core tar
|
||||
inputs:
|
||||
@@ -344,7 +321,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/freebsd-x64/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/freebsd-x64/net5.0
|
||||
- publish: $(Build.ArtifactStagingDirectory)
|
||||
artifact: 'Packages'
|
||||
displayName: Publish Packages
|
||||
@@ -889,8 +866,8 @@ stages:
|
||||
sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
|
||||
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
|
||||
- bash: |
|
||||
./build.sh --backend -f net6.0 -r win-x64
|
||||
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage
|
||||
./build.sh --backend -f net5.0 -r win-x64
|
||||
TEST_DIR=_tests/net5.0/win-x64/publish/ ./test.sh Windows Unit Coverage
|
||||
displayName: Coverage Unit Tests
|
||||
- task: SonarCloudAnalyze@1
|
||||
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
||||
|
||||
54
build.sh
54
build.sh
@@ -129,7 +129,7 @@ PackageLinux()
|
||||
|
||||
echo "Adding Radarr.Mono to UpdatePackage"
|
||||
cp $folder/Radarr.Mono.* $folder/Radarr.Update
|
||||
if [ "$framework" = "net6.0" ]; then
|
||||
if [ "$framework" = "net5.0" ]; then
|
||||
cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update
|
||||
cp $folder/libMonoPosixHelper.* $folder/Radarr.Update
|
||||
fi
|
||||
@@ -140,13 +140,12 @@ PackageLinux()
|
||||
PackageMacOS()
|
||||
{
|
||||
local framework="$1"
|
||||
local runtime="$2"
|
||||
|
||||
ProgressStart "Creating MacOS Package for $framework $runtime"
|
||||
ProgressStart "Creating MacOS Package for $framework"
|
||||
|
||||
local folder=$artifactsFolder/$runtime/$framework/Radarr
|
||||
local folder=$artifactsFolder/macos/$framework/Radarr
|
||||
|
||||
PackageFiles "$folder" "$framework" "$runtime"
|
||||
PackageFiles "$folder" "$framework" "osx-x64"
|
||||
|
||||
echo "Removing Service helpers"
|
||||
rm -f $folder/ServiceUninstall.*
|
||||
@@ -157,7 +156,7 @@ PackageMacOS()
|
||||
|
||||
echo "Adding Radarr.Mono to UpdatePackage"
|
||||
cp $folder/Radarr.Mono.* $folder/Radarr.Update
|
||||
if [ "$framework" = "net6.0" ]; then
|
||||
if [ "$framework" = "net5.0" ]; then
|
||||
cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update
|
||||
cp $folder/libMonoPosixHelper.* $folder/Radarr.Update
|
||||
fi
|
||||
@@ -168,11 +167,10 @@ PackageMacOS()
|
||||
PackageMacOSApp()
|
||||
{
|
||||
local framework="$1"
|
||||
local runtime="$2"
|
||||
|
||||
ProgressStart "Creating macOS App Package for $framework $runtime"
|
||||
ProgressStart "Creating macOS App Package for $framework"
|
||||
|
||||
local folder="$artifactsFolder/$runtime-app/$framework"
|
||||
local folder=$artifactsFolder/macos-app/$framework
|
||||
|
||||
rm -rf $folder
|
||||
mkdir -p $folder
|
||||
@@ -180,7 +178,7 @@ PackageMacOSApp()
|
||||
mkdir -p $folder/Radarr.app/Contents/MacOS
|
||||
|
||||
echo "Copying Binaries"
|
||||
cp -r $artifactsFolder/$runtime/$framework/Radarr/* $folder/Radarr.app/Contents/MacOS
|
||||
cp -r $artifactsFolder/macos/$framework/Radarr/* $folder/Radarr.app/Contents/MacOS
|
||||
|
||||
echo "Removing Update Folder"
|
||||
rm -r $folder/Radarr.app/Contents/MacOS/Radarr.Update
|
||||
@@ -227,8 +225,8 @@ Package()
|
||||
PackageWindows "$framework" "$runtime"
|
||||
;;
|
||||
osx)
|
||||
PackageMacOS "$framework" "$runtime"
|
||||
PackageMacOSApp "$framework" "$runtime"
|
||||
PackageMacOS "$framework"
|
||||
PackageMacOSApp "$framework"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -328,14 +326,14 @@ then
|
||||
Build
|
||||
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
|
||||
then
|
||||
PackageTests "net6.0" "win-x64"
|
||||
PackageTests "net6.0" "win-x86"
|
||||
PackageTests "net6.0" "linux-x64"
|
||||
PackageTests "net6.0" "linux-musl-x64"
|
||||
PackageTests "net6.0" "osx-x64"
|
||||
PackageTests "net5.0" "win-x64"
|
||||
PackageTests "net5.0" "win-x86"
|
||||
PackageTests "net5.0" "linux-x64"
|
||||
PackageTests "net5.0" "linux-musl-x64"
|
||||
PackageTests "net5.0" "osx-x64"
|
||||
if [ "$ENABLE_BSD" = "YES" ];
|
||||
then
|
||||
PackageTests "net6.0" "freebsd-x64"
|
||||
PackageTests "net5.0" "freebsd-x64"
|
||||
fi
|
||||
else
|
||||
PackageTests "$FRAMEWORK" "$RID"
|
||||
@@ -364,19 +362,17 @@ then
|
||||
|
||||
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
|
||||
then
|
||||
Package "net6.0" "win-x64"
|
||||
Package "net6.0" "win-x86"
|
||||
Package "net6.0" "linux-x64"
|
||||
Package "net6.0" "linux-musl-x64"
|
||||
Package "net6.0" "linux-arm64"
|
||||
Package "net6.0" "linux-musl-arm64"
|
||||
Package "net6.0" "linux-arm"
|
||||
Package "net6.0" "linux-musl-arm"
|
||||
Package "net6.0" "osx-x64"
|
||||
Package "net6.0" "osx-arm64"
|
||||
Package "net5.0" "win-x64"
|
||||
Package "net5.0" "win-x86"
|
||||
Package "net5.0" "linux-x64"
|
||||
Package "net5.0" "linux-musl-x64"
|
||||
Package "net5.0" "linux-arm64"
|
||||
Package "net5.0" "linux-musl-arm64"
|
||||
Package "net5.0" "linux-arm"
|
||||
Package "net5.0" "osx-x64"
|
||||
if [ "$ENABLE_BSD" = "YES" ];
|
||||
then
|
||||
Package "net6.0" "freebsd-x64"
|
||||
Package "net5.0" "freebsd-x64"
|
||||
fi
|
||||
else
|
||||
Package "$FRAMEWORK" "$RID"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
],
|
||||
"ignoreFiles": [
|
||||
"frontend/src/Styles/scaffolding.css",
|
||||
"**/Theme.Park/**/*.css",
|
||||
"**/*.js"
|
||||
],
|
||||
"rules": {
|
||||
|
||||
@@ -124,6 +124,20 @@ module.exports = (env) => {
|
||||
{
|
||||
source: 'frontend/src/Content/robots.txt',
|
||||
destination: path.join(distFolder, 'Content/robots.txt')
|
||||
},
|
||||
|
||||
// Theme.Park
|
||||
{
|
||||
source: 'frontend/src/Content/Theme.Park/*',
|
||||
destination: path.join(distFolder, 'Content/Theme.Park')
|
||||
},
|
||||
{
|
||||
source: 'frontend/src/Content/Theme.Park/Themes/*',
|
||||
destination: path.join(distFolder, 'Content/Theme.Park/Themes')
|
||||
},
|
||||
{
|
||||
source: 'frontend/src/Content/Theme.Park/Resources/*',
|
||||
destination: path.join(distFolder, 'Content/Theme.Park/Resources')
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -243,6 +257,19 @@ module.exports = (env) => {
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
test: /\.(png)?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
emitFile: false,
|
||||
name: 'Content/Theme.Park/Resources/[name].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
29
frontend/src/App/ThemeSelector.js
Normal file
29
frontend/src/App/ThemeSelector.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
const theme = window.Radarr.theme;
|
||||
|
||||
function ThemeSelector({ children }) {
|
||||
return (
|
||||
<>
|
||||
{
|
||||
theme !== 'default' &&
|
||||
<>
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href={'/Content/Theme.Park/radarr-base.css'}
|
||||
/>
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href={`/Content/Theme.Park/Themes/${theme}.css`}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
ThemeSelector.propTypes = {
|
||||
children: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default ThemeSelector;
|
||||
@@ -16,9 +16,4 @@
|
||||
color: #3a3f51;
|
||||
font-size: 21px;
|
||||
line-height: inherit;
|
||||
|
||||
&.small {
|
||||
color: #909293;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import styles from './FieldSet.css';
|
||||
|
||||
class FieldSet extends Component {
|
||||
@@ -11,14 +9,13 @@ class FieldSet extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
size,
|
||||
legend,
|
||||
children
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<fieldset className={styles.fieldSet}>
|
||||
<legend className={classNames(styles.legend, (size === sizes.SMALL) && styles.small)}>
|
||||
<legend className={styles.legend}>
|
||||
{legend}
|
||||
</legend>
|
||||
{children}
|
||||
@@ -29,13 +26,8 @@ class FieldSet extends Component {
|
||||
}
|
||||
|
||||
FieldSet.propTypes = {
|
||||
size: PropTypes.oneOf(sizes.all).isRequired,
|
||||
legend: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
FieldSet.defaultProps = {
|
||||
size: sizes.MEDIUM
|
||||
};
|
||||
|
||||
export default FieldSet;
|
||||
|
||||
@@ -113,6 +113,8 @@ function FormInputGroup(props) {
|
||||
helpTexts,
|
||||
helpTextWarning,
|
||||
helpLink,
|
||||
inlineLink,
|
||||
tooltip,
|
||||
pending,
|
||||
errors,
|
||||
warnings,
|
||||
@@ -182,6 +184,9 @@ function FormInputGroup(props) {
|
||||
!checkInput && helpText &&
|
||||
<FormInputHelpText
|
||||
text={helpText}
|
||||
link={inlineLink}
|
||||
tooltip={tooltip}
|
||||
isWarning={!!inlineLink}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -263,6 +268,8 @@ FormInputGroup.propTypes = {
|
||||
helpTexts: PropTypes.arrayOf(PropTypes.string),
|
||||
helpTextWarning: PropTypes.string,
|
||||
helpLink: PropTypes.string,
|
||||
inlineLink: PropTypes.string,
|
||||
tooltip: PropTypes.string,
|
||||
pending: PropTypes.bool,
|
||||
errors: PropTypes.arrayOf(PropTypes.object),
|
||||
warnings: PropTypes.arrayOf(PropTypes.object)
|
||||
|
||||
@@ -64,7 +64,6 @@ function ProviderFieldFormGroup(props) {
|
||||
label,
|
||||
helpText,
|
||||
helpLink,
|
||||
placeholder,
|
||||
value,
|
||||
type,
|
||||
advanced,
|
||||
@@ -97,7 +96,6 @@ function ProviderFieldFormGroup(props) {
|
||||
label={label}
|
||||
helpText={helpText}
|
||||
helpLink={helpLink}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
values={getSelectValues(selectOptions)}
|
||||
errors={errors}
|
||||
@@ -123,7 +121,6 @@ ProviderFieldFormGroup.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
helpText: PropTypes.string,
|
||||
helpLink: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
value: PropTypes.any,
|
||||
type: PropTypes.string.isRequired,
|
||||
advanced: PropTypes.bool.isRequired,
|
||||
|
||||
BIN
frontend/src/Content/Theme.Park/Resources/blur-noise.png
Normal file
BIN
frontend/src/Content/Theme.Park/Resources/blur-noise.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 92 KiB |
30
frontend/src/Content/Theme.Park/Themes/aquamarine.css
Normal file
30
frontend/src/Content/Theme.Park/Themes/aquamarine.css
Normal file
@@ -0,0 +1,30 @@
|
||||
:root {
|
||||
--main-bg-color: radial-gradient(ellipse at center, #47918a 0%, #0b3161 100%) center center/cover no-repeat fixed;
|
||||
|
||||
--modal-bg-color: radial-gradient(ellipse at top, #47918a 0%, #0b3161 100%) center center/cover no-repeat fixed;
|
||||
--modal-header-color: radial-gradient(ellipse at top, #47918a 0%, #0b3161 100%) center center/cover no-repeat fixed;
|
||||
--modal-footer-color: radial-gradient(ellipse at top, #47918a 0%, #0b3161 100%) center center/cover no-repeat fixed;
|
||||
|
||||
--drop-down-menu-bg: radial-gradient(ellipse at top, #47918a 0%, #0b3161 100%) center center/cover no-repeat fixed;
|
||||
|
||||
--button-color: #009688;
|
||||
--button-color-hover: #12afa0;
|
||||
--button-text: #eee;
|
||||
--button-text-hover: #FFF;
|
||||
|
||||
--accent-color: 18, 175, 160;
|
||||
--accent-color-hover: rgb(var(--accent-color),.8);
|
||||
--link-color: #0ed2bf;
|
||||
--link-color-hover: #36e7d6;
|
||||
--label-text-color: #fff;
|
||||
|
||||
--text:#ddd;
|
||||
--text-hover: #fff;
|
||||
--text-muted: #999;
|
||||
|
||||
/*Specials*/
|
||||
--arr-queue-color: #009688; /* Servarr apps + Bazarr*/
|
||||
--plex-poster-unwatched: rgb(21, 213, 194);
|
||||
--petio-spinner: invert(39%) sepia(98%) saturate(527%) hue-rotate(129deg) brightness(94%) contrast(101%); /* Made with https://codepen.io/jsm91/embed/ZEEawyZ */
|
||||
--gitea-color-primary-dark-4: 18, 175, 160;
|
||||
}
|
||||
30
frontend/src/Content/Theme.Park/Themes/dark.css
Normal file
30
frontend/src/Content/Theme.Park/Themes/dark.css
Normal file
@@ -0,0 +1,30 @@
|
||||
:root {
|
||||
--main-bg-color: radial-gradient(circle, #3a3a3a, #2d2d2d, #202020, #141414, #000000) center center/cover no-repeat fixed;
|
||||
|
||||
--modal-bg-color: radial-gradient(circle , #3a3a3a, #2d2d2d, #202020, #141414, #000000) center center/cover no-repeat fixed;
|
||||
--modal-header-color: radial-gradient(circle , #3a3a3a, #2d2d2d, #202020, #141414, #000000) center center/cover no-repeat fixed;
|
||||
--modal-footer-color: radial-gradient(circle , #3a3a3a, #2d2d2d, #202020, #141414, #000000) center center/cover no-repeat fixed;
|
||||
|
||||
--drop-down-menu-bg: #2d2d2d;
|
||||
|
||||
--button-color: #7a7a7a;
|
||||
--button-color-hover: #9b9b9b;
|
||||
--button-text: #eee;
|
||||
--button-text-hover: #FFF;
|
||||
|
||||
--accent-color: 170, 170, 170;
|
||||
--accent-color-hover: rgba(255, 255, 255, 0.45);
|
||||
--link-color: #7a7a7a;
|
||||
--link-color-hover: #fff;
|
||||
--label-text-color: black;
|
||||
|
||||
--text:#ddd;
|
||||
--text-hover: #fff;
|
||||
--text-muted: #999;
|
||||
|
||||
/*Specials*/
|
||||
--arr-queue-color: #6b5; /* Servarr apps + Bazarr*/
|
||||
--plex-poster-unwatched: #e5a00d;
|
||||
--petio-spinner: invert(35%) sepia(12%) saturate(4%) hue-rotate(2deg) brightness(104%) contrast(86%);/* Made with https://codepen.io/jsm91/embed/ZEEawyZ */
|
||||
--gitea-color-primary-dark-4: 255, 255, 255;
|
||||
}
|
||||
30
frontend/src/Content/Theme.Park/Themes/dracula.css
Normal file
30
frontend/src/Content/Theme.Park/Themes/dracula.css
Normal file
@@ -0,0 +1,30 @@
|
||||
:root {
|
||||
--main-bg-color: #282a36;
|
||||
|
||||
--modal-bg-color: #1e2029;
|
||||
--modal-header-color: #1e2029;
|
||||
--modal-footer-color: #1e2029;
|
||||
|
||||
--drop-down-menu-bg: #1e2029;
|
||||
|
||||
--button-color: #bd93f9;
|
||||
--button-color-hover: #ff79c6;
|
||||
--button-text: #eee;
|
||||
--button-text-hover: #FFF;
|
||||
|
||||
--accent-color: 80, 250, 123;
|
||||
--accent-color-hover: rgb(var(--accent-color),.8);
|
||||
--link-color: #ff79c6;
|
||||
--link-color-hover: #8be9fd;
|
||||
--label-text-color: #282a36;
|
||||
|
||||
--text:#6272a4;
|
||||
--text-hover: #95adfa;
|
||||
--text-muted: #999;
|
||||
|
||||
/*Specials*/
|
||||
--arr-queue-color: #50fa7b; /* Servarr apps + Bazarr*/
|
||||
--plex-poster-unwatched: #bd93f9;
|
||||
--petio-spinner: invert(79%) sepia(27%) saturate(1033%) hue-rotate(74deg) brightness(104%) contrast(96%);/* Made with https://codepen.io/jsm91/embed/ZEEawyZ */
|
||||
--gitea-color-primary-dark-4: 80, 250, 123;
|
||||
}
|
||||
30
frontend/src/Content/Theme.Park/Themes/hotline.css
Normal file
30
frontend/src/Content/Theme.Park/Themes/hotline.css
Normal file
@@ -0,0 +1,30 @@
|
||||
:root {
|
||||
--main-bg-color: linear-gradient(0deg, rgba(247,101,184,1) 0%, rgb(21, 95, 165) 100%) center center/cover no-repeat fixed;
|
||||
|
||||
--modal-bg-color: linear-gradient(0deg, rgba(247,101,184,1) 0%, rgb(21, 95, 165) 100%) center center/cover no-repeat fixed;
|
||||
--modal-header-color: linear-gradient(0deg, rgba(247,101,184,1) 0%, rgb(21, 95, 165) 100%) center center/cover no-repeat fixed;
|
||||
--modal-footer-color: linear-gradient(0deg, rgba(247,101,184,1) 0%, rgb(21, 95, 165) 100%) center center/cover no-repeat fixed;
|
||||
|
||||
--drop-down-menu-bg: linear-gradient(90deg, rgba(247,101,184,1) 0%, rgba(21, 95, 165) 100%) center center/cover no-repeat fixed;
|
||||
|
||||
--button-color: #f98dc9;
|
||||
--button-color-hover: #ff4cb1;
|
||||
--button-text: #eee;
|
||||
--button-text-hover: #fff;
|
||||
|
||||
--accent-color: 249, 141, 201;
|
||||
--accent-color-hover: rgb(var(--accent-color),.8);
|
||||
--link-color:rgb(255, 179, 222);
|
||||
--link-color-hover: #d7fffe;
|
||||
--label-text-color: #fff;
|
||||
|
||||
--text:#ddd;
|
||||
--text-hover: #fff;
|
||||
--text-muted: #999;
|
||||
|
||||
/*Specials*/
|
||||
--arr-queue-color: #f98dc9; /* Servarr apps + Bazarr*/
|
||||
--plex-poster-unwatched: #f765b8;
|
||||
--petio-spinner: invert(78%) sepia(17%) saturate(4447%) hue-rotate(290deg) brightness(109%) contrast(95%); /* Made with https://codepen.io/jsm91/embed/ZEEawyZ */
|
||||
--gitea-color-primary-dark-4: 215,255,254;
|
||||
}
|
||||
30
frontend/src/Content/Theme.Park/Themes/hotpink.css
Normal file
30
frontend/src/Content/Theme.Park/Themes/hotpink.css
Normal file
@@ -0,0 +1,30 @@
|
||||
:root {
|
||||
--main-bg-color: linear-gradient(45deg, #fb3f62 0%, #204c80 37%, #004249 97%) center center/cover no-repeat fixed;
|
||||
|
||||
--modal-bg-color: radial-gradient(circle, #204c80 0%, #000 100%) center center/cover no-repeat fixed;
|
||||
--modal-header-color: radial-gradient(circle, #204c80 0%, #000 100%) center center/cover no-repeat fixed;
|
||||
--modal-footer-color: radial-gradient(circle, #204c80 0%, #000 100%) center center/cover no-repeat fixed;
|
||||
|
||||
--drop-down-menu-bg: #204c80;
|
||||
|
||||
--button-color: #fb3f62;
|
||||
--button-color-hover: #cd4164;
|
||||
--button-text: #eee;
|
||||
--button-text-hover: #FFF;
|
||||
|
||||
--accent-color: 251, 63, 98;
|
||||
--accent-color-hover: rgba(var(--accent-color), .8);
|
||||
--link-color: rgb(0, 255, 157);
|
||||
--link-color-hover: rgba(0, 255, 157, 0.8);
|
||||
--label-text-color: #282a36;
|
||||
|
||||
--text:#eee;
|
||||
--text-hover: #fff;
|
||||
--text-muted: #999;
|
||||
|
||||
--arr-queue-color: rgb(0, 255, 157);
|
||||
--plex-poster-unwatched: #fb3f62;
|
||||
--petio-spinner: invert(29%) sepia(87%) saturate(2199%) hue-rotate(331deg) brightness(115%) contrast(97%); /* Made with https://codepen.io/jsm91/embed/ZEEawyZ */
|
||||
--gitea-color-primary-dark-4: 251, 63, 98;
|
||||
}
|
||||
|
||||
30
frontend/src/Content/Theme.Park/Themes/nord.css
Normal file
30
frontend/src/Content/Theme.Park/Themes/nord.css
Normal file
@@ -0,0 +1,30 @@
|
||||
:root {
|
||||
--main-bg-color: #2E3440;
|
||||
|
||||
--modal-bg-color: #3B4252;
|
||||
--modal-header-color: #434C5E;
|
||||
--modal-footer-color: #434C5E;
|
||||
|
||||
--drop-down-menu-bg: #3B4252;
|
||||
|
||||
--button-color: #79b8ca;
|
||||
--button-color-hover: #6a9daf;
|
||||
--button-text: #2E3440;
|
||||
--button-text-hover: #D8DEE9;
|
||||
|
||||
--accent-color: 121, 184, 202;
|
||||
--accent-color-hover: rgb(var(--accent-color),.8);
|
||||
--link-color: #81A1C1;
|
||||
--link-color-hover: #88C0D0;
|
||||
--label-text-color: #222730;
|
||||
|
||||
--text:#D8DEE9;
|
||||
--text-hover: #ECEFF4;
|
||||
--text-muted: #81A1C1;
|
||||
|
||||
/*Specials*/
|
||||
--arr-queue-color: #A3BE8C; /* Servarr apps + Bazarr*/
|
||||
--plex-poster-unwatched: #D08770;
|
||||
--petio-spinner: invert(83%) sepia(9%) saturate(1787%) hue-rotate(156deg) brightness(85%) contrast(83%); /* Made with https://codepen.io/jsm91/embed/ZEEawyZ */
|
||||
--gitea-color-primary-dark-4: 121, 184, 202;
|
||||
}
|
||||
30
frontend/src/Content/Theme.Park/Themes/organizr.css
Normal file
30
frontend/src/Content/Theme.Park/Themes/organizr.css
Normal file
@@ -0,0 +1,30 @@
|
||||
:root {
|
||||
--main-bg-color: #1f1f1f;
|
||||
|
||||
--modal-bg-color: #333;
|
||||
--modal-header-color: #232323;
|
||||
--modal-footer-color: #232323;
|
||||
|
||||
--drop-down-menu-bg: #1b1b1b;
|
||||
|
||||
--button-color: #2cabe3;
|
||||
--button-color-hover: #298fbc;
|
||||
--button-text: #eee;
|
||||
--button-text-hover: #fff;
|
||||
|
||||
--accent-color: 44, 171, 227;
|
||||
--accent-color-hover: rgb(var(--accent-color),.8);
|
||||
--link-color: #2cabe3;
|
||||
--link-color-hover: #3cc5ff;
|
||||
--label-text-color: #fff;
|
||||
|
||||
--text:#96a2b4;
|
||||
--text-hover: #fff;
|
||||
--text-muted: #999;
|
||||
|
||||
/*Specials*/
|
||||
--arr-queue-color: #2cabe3; /* Servarr apps + Bazarr*/
|
||||
--plex-poster-unwatched: #2cabe3;
|
||||
--petio-spinner: invert(65%) sepia(83%) saturate(2026%) hue-rotate(167deg) brightness(90%) contrast(97%);/* Made with https://codepen.io/jsm91/embed/ZEEawyZ */
|
||||
--gitea-color-primary-dark-4: 44, 171, 227;
|
||||
}
|
||||
30
frontend/src/Content/Theme.Park/Themes/overseerr.css
Normal file
30
frontend/src/Content/Theme.Park/Themes/overseerr.css
Normal file
@@ -0,0 +1,30 @@
|
||||
:root {
|
||||
--main-bg-color: linear-gradient(360deg, hsl(221, 39%, 11%) 65%, hsl(215, 28%, 17%) 100%);
|
||||
|
||||
--modal-bg-color: #1f2937;
|
||||
--modal-header-color: #1f2937;
|
||||
--modal-footer-color: #1f2937;
|
||||
|
||||
--drop-down-menu-bg: #374151;
|
||||
|
||||
--button-color: #4f46e5;
|
||||
--button-color-hover: #6366f1;
|
||||
--button-text: #e5e7eb;
|
||||
--button-text-hover: #fff;
|
||||
|
||||
--accent-color: 167, 139, 250;
|
||||
--accent-color-hover: rgb(var(--accent-color),.8);
|
||||
--link-color: #6366f1;
|
||||
--link-color-hover: #a78bfa;
|
||||
--label-text-color: #000;
|
||||
|
||||
--text: #d1d5db;
|
||||
--text-hover: #fff;
|
||||
--text-muted: #9ca3af;
|
||||
|
||||
/*Specials*/
|
||||
--arr-queue-color: #6366f1; /* Servarr apps + Bazarr*/
|
||||
--plex-poster-unwatched: #6366f1;
|
||||
--petio-spinner: invert(24%) sepia(59%) saturate(3411%) hue-rotate(237deg) brightness(91%) contrast(96%); /* Made with https://codepen.io/jsm91/embed/ZEEawyZ */
|
||||
--gitea-color-primary-dark-4: 98, 116, 145;
|
||||
}
|
||||
28
frontend/src/Content/Theme.Park/Themes/plex.css
Normal file
28
frontend/src/Content/Theme.Park/Themes/plex.css
Normal file
@@ -0,0 +1,28 @@
|
||||
:root {
|
||||
--main-bg-color: url("Resources/blur-noise.png") repeat scroll 0% 0%, radial-gradient(circle at 0% 100%, rgba(54, 66, 84, 0.55) 0%, rgba(54, 66, 84, 0.043) 70%, rgba(54, 66, 84, 0) 80%), radial-gradient(circle at 100% 100%, rgba(113, 135, 153, 0.55) 0%, rgba(113, 135, 153, 0.043) 70%, rgba(113, 135, 153, 0) 80%), radial-gradient(circle at 100% 0%, rgba(54, 66, 84, 0.55) 0%, rgba(54, 66, 84, 0.043) 70%, rgba(54, 66, 84, 0) 80%), radial-gradient(circle at 0% 0%, rgba(91, 114, 135, 0.55) 0%, rgba(91, 114, 135, 0.043) 70%, rgba(91, 114, 135, 0) 80%), rgb(0, 0, 0) center center/cover no-repeat fixed;
|
||||
|
||||
--modal-bg-color: #1f2326;
|
||||
--modal-header-color: #1f2326;
|
||||
--modal-footer-color: #323232;
|
||||
|
||||
--drop-down-menu-bg: #191a1c;
|
||||
|
||||
--button-color: #cc7b19;
|
||||
--button-color-hover: #e59029;
|
||||
--button-text: #eee;
|
||||
--button-text-hover: #fff;
|
||||
|
||||
--accent-color: 229, 160, 13;
|
||||
--accent-color-hover: #ffc107;
|
||||
--link-color: #e5a00d;
|
||||
--link-color-hover: #fff;
|
||||
--label-text-color: #fff;
|
||||
|
||||
--text:#ddd;
|
||||
--text-hover: #fff;
|
||||
--text-muted: #999;
|
||||
|
||||
--arr-queue-color: #27c24c;
|
||||
--petio-spinner: invert(0%) sepia(0%) saturate(100%) hue-rotate(0deg) brightness(100%) contrast(100%);
|
||||
--gitea-color-primary-dark-4: 255, 193, 7;
|
||||
}
|
||||
124
frontend/src/Content/Theme.Park/Themes/radarr-darker.css
Normal file
124
frontend/src/Content/Theme.Park/Themes/radarr-darker.css
Normal file
@@ -0,0 +1,124 @@
|
||||
:root {
|
||||
--main-bg-color: #454545;
|
||||
|
||||
--modal-bg-color: #595959;
|
||||
--modal-header-color: #595959;
|
||||
--modal-footer-color: #595959;
|
||||
|
||||
--drop-down-menu-bg: #606060;
|
||||
|
||||
--button-color: #5899eb;
|
||||
--button-color-hover: #4b91ea;
|
||||
--button-text: #eee;
|
||||
--button-text-hover: #fff;
|
||||
|
||||
--accent-color: 255, 194, 48;
|
||||
--accent-color-hover: rgb(255, 194, 48, .8);
|
||||
--link-color: rgb(255, 194, 48);
|
||||
--link-color-hover: rgb(255, 194, 48, .8);
|
||||
--label-text-color: #eee;
|
||||
|
||||
--text: #ccc;
|
||||
--text-hover: #fff;
|
||||
--text-muted: #999;
|
||||
|
||||
/*Specials*/
|
||||
--arr-queue-color: #5d9cec;
|
||||
--side-menu-active: #333333;
|
||||
--scroller-hover: #606060;
|
||||
--scroller: #707070;
|
||||
--border-color: #606060;
|
||||
--label-color: #707070;
|
||||
--label-info: #5d9cec;
|
||||
--header-color: #464b51;
|
||||
--side-menu-color: #595959;
|
||||
}
|
||||
|
||||
/* HEADER */
|
||||
|
||||
[class*="PageHeader-header-"] {
|
||||
background-color: var(--header-color);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
[class*="PageToolbar-toolbar-"] {
|
||||
background-color: #707070;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* SIDE MENU */
|
||||
|
||||
[class*="PageSidebar-sidebar-"] {
|
||||
background-color: var(--side-menu-color);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
[class*=PageSidebarItem-link-]:focus {
|
||||
color: rgb(var(--accent-color)) !important;
|
||||
}
|
||||
|
||||
[class*=PageSidebarItem-isActiveLink-] {
|
||||
color: var(--link-color) !important;
|
||||
}
|
||||
|
||||
[class*=PageSidebarItem-isActiveParentLink-] {
|
||||
background-color: var(--side-menu-active);
|
||||
}
|
||||
|
||||
/* SCROLLER */
|
||||
[class*=ImportSeriesSelectSeries-results-]::-webkit-scrollbar-thumb:hover,
|
||||
[class*=OverlayScroller-thumb-]:hover {
|
||||
background-color: var(--scroller-hover) !important;
|
||||
}
|
||||
|
||||
[class*="OverlayScroller-thumb-"],
|
||||
[class*=Scroller-scroller-]::-webkit-scrollbar-thumb {
|
||||
background-color: var(--scroller) !important;
|
||||
}
|
||||
|
||||
/* MODALS */
|
||||
|
||||
[class*=ModalHeader-modalHeader-],
|
||||
[class*=FieldSet-legend-] {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
[class*=ModalFooter-modalFooter-] {
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
/* LABLES */
|
||||
|
||||
[class*="Label-default-"] {
|
||||
border-color: var(--label-color);
|
||||
background-color: var(--label-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
[class*="Label-info-"]:not([class*="PageSidebarItem-status-"] [class*="Label-info-"]) {
|
||||
border-color: var(--label-info);
|
||||
background-color: var(--label-info);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* SETTINGS */
|
||||
|
||||
[class*=Settings-link-] {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
/* SEARCH DROP DOWN */
|
||||
|
||||
[class*="MovieSearchInput-containerOpen-"] [class*="MovieSearchInput-movieContainer-"] {
|
||||
border: 1px solid var(--drop-down-menu-bg);
|
||||
background-color: var(--drop-down-menu-bg);
|
||||
box-shadow: inset 0 1px 1px rgb(0 0 0 / 8%);
|
||||
color: #e1e2e3;
|
||||
}
|
||||
|
||||
/* SERIES PAGE */
|
||||
|
||||
[class*="MovieIndexPoster-controls-"] {
|
||||
background-color: var(--label-color) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
30
frontend/src/Content/Theme.Park/Themes/space-gray.css
Normal file
30
frontend/src/Content/Theme.Park/Themes/space-gray.css
Normal file
@@ -0,0 +1,30 @@
|
||||
:root {
|
||||
--main-bg-color: radial-gradient(ellipse at center, rgba(87, 108, 117, 1) 0%, rgba(37, 50, 55, 1) 100.2%) center center/cover no-repeat fixed;
|
||||
|
||||
--modal-bg-color: radial-gradient(ellipse at top, rgba(87, 108, 117, 1) 0%, rgba(37, 50, 55, 1) 100.2%) center center/cover no-repeat fixed;
|
||||
--modal-header-color: radial-gradient(ellipse at top, rgba(87, 108, 117, 1) 0%, rgba(37, 50, 55, 1) 100.2%) center center/cover no-repeat fixed;
|
||||
--modal-footer-color: radial-gradient(ellipse at top, rgba(87, 108, 117, 1) 0%, rgba(37, 50, 55, 1) 100.2%) center center/cover no-repeat fixed;
|
||||
|
||||
--drop-down-menu-bg: radial-gradient(ellipse at top, rgba(87, 108, 117, 1) 0%, rgba(37, 50, 55, 1) 100.2%) center center/cover no-repeat fixed;
|
||||
|
||||
--button-color: #607D8B;
|
||||
--button-color-hover: #81a6b7;
|
||||
--button-text: #eee;
|
||||
--button-text-hover: #fff;
|
||||
|
||||
--accent-color: 129, 166, 183;
|
||||
--accent-color-hover: rgb(var(--accent-color),.8);
|
||||
--link-color: #81a6b7;
|
||||
--link-color-hover: #9adfff;
|
||||
--label-text-color: #fff;
|
||||
|
||||
--text:#bbb;
|
||||
--text-hover: #fff;
|
||||
--text-muted: #999;
|
||||
|
||||
/*Specials*/
|
||||
--arr-queue-color: #81a6b7; /* Servarr apps + Bazarr*/
|
||||
--plex-poster-unwatched: #70aeca;
|
||||
--petio-spinner: invert(50%) sepia(31%) saturate(341%) hue-rotate(155deg) brightness(88%) contrast(85%);/* Made with https://codepen.io/jsm91/embed/ZEEawyZ */
|
||||
--gitea-color-primary-dark-4: 129, 166, 183;
|
||||
}
|
||||
1101
frontend/src/Content/Theme.Park/radarr-base.css
Normal file
1101
frontend/src/Content/Theme.Park/radarr-base.css
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
@@ -14,7 +13,7 @@ import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './EditDownloadClientModalContent.css';
|
||||
|
||||
@@ -46,10 +45,7 @@ class EditDownloadClientModalContent extends Component {
|
||||
implementationName,
|
||||
name,
|
||||
enable,
|
||||
protocol,
|
||||
priority,
|
||||
removeCompletedDownloads,
|
||||
removeFailedDownloads,
|
||||
fields,
|
||||
message
|
||||
} = item;
|
||||
@@ -140,38 +136,6 @@ class EditDownloadClientModalContent extends Component {
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FieldSet
|
||||
size={sizes.SMALL}
|
||||
legend={translate('CompletedDownloadHandling')}
|
||||
>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('RemoveCompleted')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="removeCompletedDownloads"
|
||||
helpText={translate('RemoveCompletedDownloadsHelpText')}
|
||||
{...removeCompletedDownloads}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{
|
||||
protocol.value !== 'torrent' &&
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('RemoveFailed')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="removeFailedDownloads"
|
||||
helpText={translate('RemoveFailedDownloadsHelpText')}
|
||||
{...removeFailedDownloads}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
}
|
||||
</FieldSet>
|
||||
|
||||
</Form>
|
||||
}
|
||||
</ModalBody>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import { inputTypes, sizes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
function DownloadClientOptions(props) {
|
||||
@@ -35,15 +34,11 @@ function DownloadClientOptions(props) {
|
||||
}
|
||||
|
||||
{
|
||||
hasSettings && !isFetching && !error && advancedSettings &&
|
||||
hasSettings && !isFetching && !error &&
|
||||
<div>
|
||||
<FieldSet legend={translate('CompletedDownloadHandling')}>
|
||||
<Form>
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
<FormGroup size={sizes.MEDIUM}>
|
||||
<FormLabel>{translate('Enable')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
@@ -55,6 +50,22 @@ function DownloadClientOptions(props) {
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
<FormLabel>{translate('Remove')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="removeCompletedDownloads"
|
||||
helpText={translate('RemoveCompletedDownloadsHelpText')}
|
||||
onChange={onInputChange}
|
||||
{...settings.removeCompletedDownloads}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
@@ -91,10 +102,23 @@ function DownloadClientOptions(props) {
|
||||
{...settings.autoRedownloadFailed}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
<FormLabel>{translate('Remove')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="removeFailedDownloads"
|
||||
helpText={translate('RemoveFailedDownloadsHelpText')}
|
||||
onChange={onInputChange}
|
||||
{...settings.removeFailedDownloads}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('RemoveDownloadsAlert')}
|
||||
</Alert>
|
||||
</FieldSet>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -48,6 +48,21 @@ export const movieRuntimeFormatOptions = [
|
||||
{ key: 'minutes', value: '75 mins' }
|
||||
];
|
||||
|
||||
export const themeOptions = [
|
||||
{ key: 'default', value: 'Default' },
|
||||
{ key: 'aquamarine', value: 'Aquamarine' },
|
||||
{ key: 'dark', value: 'Dark' },
|
||||
{ key: 'dracula', value: 'Dracula' },
|
||||
{ key: 'hotline', value: 'Hotline' },
|
||||
{ key: 'hotpink', value: 'Hotpink' },
|
||||
{ key: 'nord', value: 'Nord' },
|
||||
{ key: 'organizr', value: 'Organizr' },
|
||||
{ key: 'overseerr', value: 'Overseerr' },
|
||||
{ key: 'plex', value: 'Plex' },
|
||||
{ key: 'radarr-darker', value: 'Radarr Darker' },
|
||||
{ key: 'space-gray', value: 'Space Gray' }
|
||||
];
|
||||
|
||||
class UISettings extends Component {
|
||||
|
||||
//
|
||||
@@ -184,6 +199,21 @@ class UISettings extends Component {
|
||||
</FieldSet>
|
||||
|
||||
<FieldSet legend={translate('Style')}>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Theme')}</FormLabel>
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="theme"
|
||||
helpText={translate('ThemeHelpText', ['Theme.Park'])}
|
||||
inlineLink="https://github.com/GilbN/theme.park"
|
||||
tooltip="Theme.Park Github"
|
||||
helpTextWarning={translate('ThemeHelpTextWarning')}
|
||||
values={themeOptions}
|
||||
onChange={onInputChange}
|
||||
{...settings.theme}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('SettingsEnableColorImpairedMode')}</FormLabel>
|
||||
<FormInputGroup
|
||||
|
||||
@@ -14,7 +14,6 @@ function createRemoveItemHandler(section, url) {
|
||||
|
||||
const ajaxOptions = {
|
||||
url: `${url}/${id}?${$.param(queryParams, true)}`,
|
||||
dataType: 'text',
|
||||
method: 'DELETE'
|
||||
};
|
||||
|
||||
|
||||
@@ -600,7 +600,6 @@ export const actionHandlers = handleThunks({
|
||||
const promise = createAjaxRequest({
|
||||
url: '/exclusions/bulk',
|
||||
method: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(exclusions)
|
||||
}).request;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createBrowserHistory } from 'history';
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import ThemeSelector from 'App/ThemeSelector';
|
||||
import createAppStore from 'Store/createAppStore';
|
||||
import App from './App/App';
|
||||
|
||||
@@ -13,9 +14,11 @@ const history = createBrowserHistory();
|
||||
const store = createAppStore(history);
|
||||
|
||||
render(
|
||||
<App
|
||||
store={store}
|
||||
history={history}
|
||||
/>,
|
||||
<ThemeSelector>
|
||||
<App
|
||||
store={store}
|
||||
history={history}
|
||||
/>
|
||||
</ThemeSelector>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
||||
10
package.json
10
package.json
@@ -30,7 +30,7 @@
|
||||
"@fortawesome/free-regular-svg-icons": "5.15.3",
|
||||
"@fortawesome/free-solid-svg-icons": "5.15.3",
|
||||
"@fortawesome/react-fontawesome": "0.1.14",
|
||||
"@microsoft/signalr": "6.0.0",
|
||||
"@microsoft/signalr": "5.0.10",
|
||||
"@sentry/browser": "6.13.2",
|
||||
"@sentry/integrations": "6.13.2",
|
||||
"classnames": "2.3.1",
|
||||
@@ -82,7 +82,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.13.16",
|
||||
"@babel/eslint-parser": "7.13.14",
|
||||
"@babel/plugin-proposal-class-properties": "7.13.0",
|
||||
"@babel/plugin-proposal-decorators": "7.13.15",
|
||||
"@babel/plugin-proposal-export-default-from": "7.12.13",
|
||||
@@ -95,6 +94,7 @@
|
||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||
"@babel/preset-env": "7.13.15",
|
||||
"@babel/preset-react": "7.13.13",
|
||||
"@babel/eslint-parser": "7.13.14",
|
||||
"autoprefixer": "10.2.5",
|
||||
"babel-loader": "8.2.2",
|
||||
"babel-plugin-inline-classnames": "2.0.1",
|
||||
@@ -125,12 +125,12 @@
|
||||
"run-sequence": "2.2.1",
|
||||
"streamqueue": "1.1.2",
|
||||
"style-loader": "2.0.0",
|
||||
"stylelint": "13.13.0",
|
||||
"stylelint-order": "4.1.0",
|
||||
"url-loader": "4.1.1",
|
||||
"webpack": "5.35.1",
|
||||
"webpack-cli": "4.6.0",
|
||||
"webpack-livereload-plugin": "3.0.1",
|
||||
"worker-loader": "3.0.8"
|
||||
"worker-loader": "3.0.8",
|
||||
"stylelint": "13.13.0",
|
||||
"stylelint-order": "4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
<!-- Common to all Radarr Projects -->
|
||||
<PropertyGroup>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
||||
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;osx-x64;osx-arm64;linux-x64;linux-musl-x64;linux-arm;linux-musl-arm;linux-arm64;linux-musl-arm64</RuntimeIdentifiers>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;osx-x64;linux-x64;linux-musl-x64;linux-arm;linux-arm64;linux-musl-arm64</RuntimeIdentifiers>
|
||||
|
||||
<RadarrRootDir>$(MSBuildThisFileDirectory)..\</RadarrRootDir>
|
||||
|
||||
@@ -90,13 +89,13 @@
|
||||
|
||||
<!-- Standard testing packages -->
|
||||
<ItemGroup Condition="'$(TestProject)'=='true'">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
<PackageReference Include="NunitXml.TestLogger" Version="3.0.117" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Include="NunitXml.TestLogger" Version="2.1.62" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TestProject)'=='true' and '$(TargetFramework)'=='net6.0'">
|
||||
<ItemGroup Condition="'$(TestProject)'=='true' and '$(TargetFramework)'=='net5.0'">
|
||||
<PackageReference Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net5.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NBuilder" Version="6.1.0" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net5.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Selenium.Support" Version="3.141.0" />
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
@@ -16,11 +15,8 @@ using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Http.Dispatchers;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Common.TPL;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Security;
|
||||
using NzbDrone.Test.Common;
|
||||
using NzbDrone.Test.Common.Categories;
|
||||
using HttpClient = NzbDrone.Common.Http.HttpClient;
|
||||
|
||||
namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
@@ -35,8 +31,6 @@ namespace NzbDrone.Common.Test.Http
|
||||
private string _httpBinHost;
|
||||
private string _httpBinHost2;
|
||||
|
||||
private System.Net.Http.HttpClient _httpClient = new ();
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void FixtureSetUp()
|
||||
{
|
||||
@@ -44,7 +38,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
var mainHost = "httpbin.servarr.com";
|
||||
|
||||
// Use mirrors for tests that use two hosts
|
||||
var candidates = new[] { "httpbin1.servarr.com" };
|
||||
var candidates = new[] { "eu.httpbin.org", /* "httpbin.org", */ "www.httpbin.org" };
|
||||
|
||||
// httpbin.org is broken right now, occassionally redirecting to https if it's unavailable.
|
||||
_httpBinHost = mainHost;
|
||||
@@ -52,20 +46,29 @@ namespace NzbDrone.Common.Test.Http
|
||||
|
||||
TestLogger.Info($"{candidates.Length} TestSites available.");
|
||||
|
||||
_httpBinSleep = 10;
|
||||
_httpBinSleep = _httpBinHosts.Length < 2 ? 100 : 10;
|
||||
}
|
||||
|
||||
private bool IsTestSiteAvailable(string site)
|
||||
{
|
||||
try
|
||||
{
|
||||
var res = _httpClient.GetAsync($"https://{site}/get").GetAwaiter().GetResult();
|
||||
var req = WebRequest.Create($"https://{site}/get") as HttpWebRequest;
|
||||
var res = req.GetResponse() as HttpWebResponse;
|
||||
if (res.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
res = _httpClient.GetAsync($"https://{site}/status/429").GetAwaiter().GetResult();
|
||||
try
|
||||
{
|
||||
req = WebRequest.Create($"https://{site}/status/429") as HttpWebRequest;
|
||||
res = req.GetResponse() as HttpWebResponse;
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
res = ex.Response as HttpWebResponse;
|
||||
}
|
||||
|
||||
if (res == null || res.StatusCode != (HttpStatusCode)429)
|
||||
{
|
||||
@@ -92,13 +95,10 @@ namespace NzbDrone.Common.Test.Http
|
||||
Mocker.GetMock<IOsInfo>().Setup(c => c.Name).Returns("TestOS");
|
||||
Mocker.GetMock<IOsInfo>().Setup(c => c.Version).Returns("9.0.0");
|
||||
|
||||
Mocker.GetMock<IConfigService>().SetupGet(x => x.CertificateValidation).Returns(CertificateValidationType.Enabled);
|
||||
|
||||
Mocker.SetConstant<IUserAgentBuilder>(Mocker.Resolve<UserAgentBuilder>());
|
||||
|
||||
Mocker.SetConstant<ICacheManager>(Mocker.Resolve<CacheManager>());
|
||||
Mocker.SetConstant<ICreateManagedWebProxy>(Mocker.Resolve<ManagedWebProxyFactory>());
|
||||
Mocker.SetConstant<ICertificateValidationService>(new X509CertificateValidationService(Mocker.GetMock<IConfigService>().Object, TestLogger));
|
||||
Mocker.SetConstant<IRateLimitService>(Mocker.Resolve<RateLimitService>());
|
||||
Mocker.SetConstant<IEnumerable<IHttpRequestInterceptor>>(Array.Empty<IHttpRequestInterceptor>());
|
||||
Mocker.SetConstant<IHttpDispatcher>(Mocker.Resolve<TDispatcher>());
|
||||
@@ -138,28 +138,6 @@ namespace NzbDrone.Common.Test.Http
|
||||
response.Content.Should().NotBeNullOrWhiteSpace();
|
||||
}
|
||||
|
||||
[TestCase(CertificateValidationType.Enabled)]
|
||||
[TestCase(CertificateValidationType.DisabledForLocalAddresses)]
|
||||
public void bad_ssl_should_fail_when_remote_validation_enabled(CertificateValidationType validationType)
|
||||
{
|
||||
Mocker.GetMock<IConfigService>().SetupGet(x => x.CertificateValidation).Returns(validationType);
|
||||
var request = new HttpRequest($"https://expired.badssl.com");
|
||||
|
||||
Assert.Throws<HttpRequestException>(() => Subject.Execute(request));
|
||||
ExceptionVerification.ExpectedErrors(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void bad_ssl_should_pass_if_remote_validation_disabled()
|
||||
{
|
||||
Mocker.GetMock<IConfigService>().SetupGet(x => x.CertificateValidation).Returns(CertificateValidationType.Disabled);
|
||||
|
||||
var request = new HttpRequest($"https://expired.badssl.com");
|
||||
|
||||
Subject.Execute(request);
|
||||
ExceptionVerification.ExpectedErrors(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_execute_typed_get()
|
||||
{
|
||||
@@ -184,44 +162,15 @@ namespace NzbDrone.Common.Test.Http
|
||||
response.Resource.Data.Should().Be(message);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_execute_post_with_content_type()
|
||||
[TestCase("gzip")]
|
||||
public void should_execute_get_using_gzip(string compression)
|
||||
{
|
||||
var message = "{ my: 1 }";
|
||||
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/post");
|
||||
request.SetContent(message);
|
||||
request.Headers.ContentType = "application/json";
|
||||
|
||||
var response = Subject.Post<HttpBinResource>(request);
|
||||
|
||||
response.Resource.Data.Should().Be(message);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_execute_get_using_gzip()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/gzip");
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/{compression}");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
|
||||
response.Resource.Headers["Accept-Encoding"].ToString().Should().Contain("gzip");
|
||||
|
||||
response.Resource.Headers["Accept-Encoding"].ToString().Should().Be(compression);
|
||||
response.Resource.Gzipped.Should().BeTrue();
|
||||
response.Resource.Brotli.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_execute_get_using_brotli()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/brotli");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
|
||||
response.Resource.Headers["Accept-Encoding"].ToString().Should().Contain("br");
|
||||
|
||||
response.Resource.Gzipped.Should().BeFalse();
|
||||
response.Resource.Brotli.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase(HttpStatusCode.Unauthorized)]
|
||||
@@ -388,38 +337,13 @@ namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
var file = GetTempFilePath();
|
||||
|
||||
Assert.Throws<HttpException>(() => Subject.DownloadFile("https://download.sonarr.tv/wrongpath", file));
|
||||
Assert.Throws<WebException>(() => Subject.DownloadFile("https://download.sonarr.tv/wrongpath", file));
|
||||
|
||||
File.Exists(file).Should().BeFalse();
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_write_redirect_content_to_stream()
|
||||
{
|
||||
var file = GetTempFilePath();
|
||||
|
||||
using (var fileStream = new FileStream(file, FileMode.Create))
|
||||
{
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/redirect/1");
|
||||
request.AllowAutoRedirect = false;
|
||||
request.ResponseStream = fileStream;
|
||||
|
||||
var response = Subject.Get(request);
|
||||
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Moved);
|
||||
}
|
||||
|
||||
ExceptionVerification.ExpectedErrors(1);
|
||||
|
||||
File.Exists(file).Should().BeTrue();
|
||||
|
||||
var fileInfo = new FileInfo(file);
|
||||
|
||||
fileInfo.Length.Should().Be(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_send_cookie()
|
||||
{
|
||||
@@ -819,7 +743,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = $"https://{_httpBinHost}/response-headers?Set-Cookie={Uri.EscapeDataString(malformedCookie)}";
|
||||
string url = $"https://{_httpBinHost}/response-headers?Set-Cookie={Uri.EscapeUriString(malformedCookie)}";
|
||||
|
||||
var requestSet = new HttpRequest(url);
|
||||
requestSet.AllowAutoRedirect = false;
|
||||
@@ -849,7 +773,6 @@ namespace NzbDrone.Common.Test.Http
|
||||
public string Url { get; set; }
|
||||
public string Data { get; set; }
|
||||
public bool Gzipped { get; set; }
|
||||
public bool Brotli { get; set; }
|
||||
}
|
||||
|
||||
public class HttpCookieResource
|
||||
|
||||
@@ -62,7 +62,6 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
[TestCase("Hardlinking episode file: /home/mySecret/Downloads to /media/abc.mkv")]
|
||||
[TestCase("Hardlink '/home/mySecret/Downloads/abs.mkv' to '/media/abc.mkv' failed.")]
|
||||
[TestCase("https://notifiarr.com/notifier.php: api=1234530f-422f-4aac-b6b3-01233210aaaa&radarr_health_issue_message=Download")]
|
||||
[TestCase("/readarr/signalr/messages/negotiate?access_token=1234530f422f4aacb6b301233210aaaa&negotiateVersion=1")]
|
||||
|
||||
// Announce URLs (passkeys) Magnet & Tracker
|
||||
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net5.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Host\Radarr.Host.csproj" />
|
||||
|
||||
@@ -3,8 +3,6 @@ using DryIoc;
|
||||
using DryIoc.Microsoft.DependencyInjection;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Composition.Extensions;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
@@ -27,15 +25,12 @@ namespace NzbDrone.Common.Test
|
||||
.AddNzbDroneLogger()
|
||||
.AutoAddServices(Bootstrap.ASSEMBLIES)
|
||||
.AddDummyDatabase()
|
||||
.AddStartupContext(new StartupContext("first", "second"));
|
||||
.AddStartupContext(new StartupContext("first", "second"))
|
||||
.GetServiceProvider();
|
||||
|
||||
container.RegisterInstance<IHostLifetime>(new Mock<IHostLifetime>().Object);
|
||||
container.GetRequiredService<IAppFolderFactory>().Register();
|
||||
|
||||
var serviceProvider = container.GetServiceProvider();
|
||||
|
||||
serviceProvider.GetRequiredService<IAppFolderFactory>().Register();
|
||||
|
||||
Mocker.SetConstant<System.IServiceProvider>(serviceProvider);
|
||||
Mocker.SetConstant<System.IServiceProvider>(container);
|
||||
|
||||
var handlers = Subject.BuildAll<IHandle<ApplicationStartedEvent>>()
|
||||
.Select(c => c.GetType().FullName);
|
||||
|
||||
@@ -106,7 +106,7 @@ namespace NzbDrone.Common
|
||||
Stream inStream = File.OpenRead(compressedFile);
|
||||
Stream gzipStream = new GZipInputStream(inStream);
|
||||
|
||||
TarArchive tarArchive = TarArchive.CreateInputTarArchive(gzipStream, null);
|
||||
TarArchive tarArchive = TarArchive.CreateInputTarArchive(gzipStream);
|
||||
tarArchive.ExtractContents(destination);
|
||||
tarArchive.Close();
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Security.Principal;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Hosting.WindowsServices;
|
||||
using System.ServiceProcess;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Processes;
|
||||
|
||||
@@ -14,13 +14,16 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
private readonly Logger _logger;
|
||||
private readonly DateTime _startTime = DateTime.UtcNow;
|
||||
|
||||
public RuntimeInfo(IHostLifetime hostLifetime, Logger logger)
|
||||
public RuntimeInfo(IServiceProvider serviceProvider, Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
IsWindowsService = hostLifetime is WindowsServiceLifetime;
|
||||
IsWindowsService = !IsUserInteractive &&
|
||||
OsInfo.IsWindows &&
|
||||
serviceProvider.ServiceExist(ServiceProvider.SERVICE_NAME) &&
|
||||
serviceProvider.GetStatus(ServiceProvider.SERVICE_NAME) == ServiceControllerStatus.StartPending;
|
||||
|
||||
// net6.0 will return Radarr.dll for entry assembly, we need the actual
|
||||
// net5.0 will return Radarr.dll for entry assembly, we need the actual
|
||||
// executable name (Radarr on linux). On mono this will return the location of
|
||||
// the mono executable itself, which is not what we want.
|
||||
var entry = Process.GetCurrentProcess().MainModule;
|
||||
|
||||
@@ -6,6 +6,13 @@ namespace NzbDrone.Common.Extensions
|
||||
{
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
|
||||
{
|
||||
var knownKeys = new HashSet<TKey>();
|
||||
|
||||
return source.Where(element => knownKeys.Add(keySelector(element)));
|
||||
}
|
||||
|
||||
public static IEnumerable<TFirst> IntersectBy<TFirst, TSecond, TKey>(this IEnumerable<TFirst> first,
|
||||
Func<TFirst, TKey> firstKeySelector,
|
||||
IEnumerable<TSecond> second,
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
using System.Net.Http;
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace NzbDrone.Common.Http.Dispatchers
|
||||
{
|
||||
public interface ICertificateValidationService
|
||||
{
|
||||
bool ShouldByPassValidationError(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors);
|
||||
}
|
||||
}
|
||||
@@ -5,5 +5,6 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
public interface IHttpDispatcher
|
||||
{
|
||||
HttpResponse GetResponse(HttpRequest request, CookieContainer cookies);
|
||||
void DownloadFile(string url, string fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NLog.Fluent;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
|
||||
@@ -15,191 +14,200 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
{
|
||||
public class ManagedHttpDispatcher : IHttpDispatcher
|
||||
{
|
||||
private const string NO_PROXY_KEY = "no-proxy";
|
||||
|
||||
private const int connection_establish_timeout = 2000;
|
||||
private static bool useIPv6 = Socket.OSSupportsIPv6;
|
||||
private static bool hasResolvedIPv6Availability;
|
||||
|
||||
private readonly IHttpProxySettingsProvider _proxySettingsProvider;
|
||||
private readonly ICreateManagedWebProxy _createManagedWebProxy;
|
||||
private readonly ICertificateValidationService _certificateValidationService;
|
||||
private readonly IUserAgentBuilder _userAgentBuilder;
|
||||
private readonly ICached<System.Net.Http.HttpClient> _httpClientCache;
|
||||
private readonly IPlatformInfo _platformInfo;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ManagedHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider,
|
||||
ICreateManagedWebProxy createManagedWebProxy,
|
||||
ICertificateValidationService certificateValidationService,
|
||||
IUserAgentBuilder userAgentBuilder,
|
||||
ICacheManager cacheManager,
|
||||
Logger logger)
|
||||
public ManagedHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider, ICreateManagedWebProxy createManagedWebProxy, IUserAgentBuilder userAgentBuilder, IPlatformInfo platformInfo, Logger logger)
|
||||
{
|
||||
_proxySettingsProvider = proxySettingsProvider;
|
||||
_createManagedWebProxy = createManagedWebProxy;
|
||||
_certificateValidationService = certificateValidationService;
|
||||
_userAgentBuilder = userAgentBuilder;
|
||||
_platformInfo = platformInfo;
|
||||
_logger = logger;
|
||||
|
||||
_httpClientCache = cacheManager.GetCache<System.Net.Http.HttpClient>(typeof(ManagedHttpDispatcher));
|
||||
}
|
||||
|
||||
public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies)
|
||||
{
|
||||
var requestMessage = new HttpRequestMessage(request.Method, (Uri)request.Url);
|
||||
requestMessage.Headers.UserAgent.ParseAdd(_userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent));
|
||||
requestMessage.Headers.ConnectionClose = !request.ConnectionKeepAlive;
|
||||
var webRequest = (HttpWebRequest)WebRequest.Create((Uri)request.Url);
|
||||
|
||||
var cookieHeader = cookies.GetCookieHeader((Uri)request.Url);
|
||||
if (cookieHeader.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
requestMessage.Headers.Add("Cookie", cookieHeader);
|
||||
}
|
||||
// Deflate is not a standard and could break depending on implementation.
|
||||
// we should just stick with the more compatible Gzip
|
||||
//http://stackoverflow.com/questions/8490718/how-to-decompress-stream-deflated-with-java-util-zip-deflater-in-net
|
||||
webRequest.AutomaticDecompression = DecompressionMethods.GZip;
|
||||
|
||||
webRequest.Method = request.Method.ToString();
|
||||
webRequest.UserAgent = _userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent);
|
||||
webRequest.KeepAlive = request.ConnectionKeepAlive;
|
||||
webRequest.AllowAutoRedirect = false;
|
||||
webRequest.CookieContainer = cookies;
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
if (request.RequestTimeout != TimeSpan.Zero)
|
||||
{
|
||||
cts.CancelAfter(request.RequestTimeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The default for System.Net.Http.HttpClient
|
||||
cts.CancelAfter(TimeSpan.FromSeconds(100));
|
||||
webRequest.Timeout = (int)Math.Ceiling(request.RequestTimeout.TotalMilliseconds);
|
||||
}
|
||||
|
||||
if (request.ContentData != null)
|
||||
{
|
||||
requestMessage.Content = new ByteArrayContent(request.ContentData);
|
||||
}
|
||||
webRequest.Proxy = GetProxy(request.Url);
|
||||
|
||||
if (request.Headers != null)
|
||||
{
|
||||
AddRequestHeaders(requestMessage, request.Headers);
|
||||
AddRequestHeaders(webRequest, request.Headers);
|
||||
}
|
||||
|
||||
var httpClient = GetClient(request.Url);
|
||||
|
||||
HttpResponseMessage responseMessage;
|
||||
HttpWebResponse httpWebResponse;
|
||||
|
||||
try
|
||||
{
|
||||
responseMessage = httpClient.Send(requestMessage, cts.Token);
|
||||
if (request.ContentData != null)
|
||||
{
|
||||
webRequest.ContentLength = request.ContentData.Length;
|
||||
using (var writeStream = webRequest.GetRequestStream())
|
||||
{
|
||||
writeStream.Write(request.ContentData, 0, request.ContentData.Length);
|
||||
}
|
||||
}
|
||||
|
||||
httpWebResponse = (HttpWebResponse)webRequest.GetResponse();
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
catch (WebException e)
|
||||
{
|
||||
_logger.Error(e, "HttpClient error");
|
||||
throw;
|
||||
httpWebResponse = (HttpWebResponse)e.Response;
|
||||
|
||||
if (httpWebResponse == null)
|
||||
{
|
||||
// The default messages for WebException on mono are pretty horrible.
|
||||
if (e.Status == WebExceptionStatus.NameResolutionFailure)
|
||||
{
|
||||
throw new WebException($"DNS Name Resolution Failure: '{webRequest.RequestUri.Host}'", e.Status);
|
||||
}
|
||||
else if (e.ToString().Contains("TLS Support not"))
|
||||
{
|
||||
throw new TlsFailureException(webRequest, e);
|
||||
}
|
||||
else if (e.ToString().Contains("The authentication or decryption has failed."))
|
||||
{
|
||||
throw new TlsFailureException(webRequest, e);
|
||||
}
|
||||
else if (OsInfo.IsNotWindows)
|
||||
{
|
||||
throw new WebException($"{e.Message}: '{webRequest.RequestUri}'", e, e.Status, e.Response);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte[] data = null;
|
||||
|
||||
using (var responseStream = responseMessage.Content.ReadAsStream())
|
||||
using (var responseStream = httpWebResponse.GetResponseStream())
|
||||
{
|
||||
if (responseStream != null && responseStream != Stream.Null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
// A target ResponseStream was specified, write to that instead.
|
||||
// But only on the OK status code, since we don't want to write failures and redirects.
|
||||
responseStream.CopyTo(request.ResponseStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
data = responseStream.ToBytes();
|
||||
}
|
||||
data = responseStream.ToBytes();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new WebException("Failed to read complete http response", ex, WebExceptionStatus.ReceiveFailure, null);
|
||||
throw new WebException("Failed to read complete http response", ex, WebExceptionStatus.ReceiveFailure, httpWebResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var headers = responseMessage.Headers.ToNameValueCollection();
|
||||
|
||||
headers.Add(responseMessage.Content.Headers.ToNameValueCollection());
|
||||
|
||||
return new HttpResponse(request, new HttpHeader(headers), data, responseMessage.StatusCode);
|
||||
return new HttpResponse(request, new HttpHeader(httpWebResponse.Headers), data, httpWebResponse.StatusCode);
|
||||
}
|
||||
|
||||
protected virtual System.Net.Http.HttpClient GetClient(HttpUri uri)
|
||||
public void DownloadFile(string url, string fileName)
|
||||
{
|
||||
var proxySettings = _proxySettingsProvider.GetProxySettings(uri);
|
||||
|
||||
var key = proxySettings?.Key ?? NO_PROXY_KEY;
|
||||
|
||||
return _httpClientCache.Get(key, () => CreateHttpClient(proxySettings));
|
||||
}
|
||||
|
||||
protected virtual System.Net.Http.HttpClient CreateHttpClient(HttpProxySettings proxySettings)
|
||||
{
|
||||
var handler = new SocketsHttpHandler()
|
||||
try
|
||||
{
|
||||
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Brotli,
|
||||
UseCookies = false, // sic - we don't want to use a shared cookie container
|
||||
AllowAutoRedirect = false,
|
||||
MaxConnectionsPerServer = 12,
|
||||
ConnectCallback = onConnect,
|
||||
SslOptions = new SslClientAuthenticationOptions
|
||||
var fileInfo = new FileInfo(fileName);
|
||||
if (fileInfo.Directory != null && !fileInfo.Directory.Exists)
|
||||
{
|
||||
RemoteCertificateValidationCallback = _certificateValidationService.ShouldByPassValidationError
|
||||
fileInfo.Directory.Create();
|
||||
}
|
||||
};
|
||||
|
||||
_logger.Debug("Downloading [{0}] to [{1}]", url, fileName);
|
||||
|
||||
var stopWatch = Stopwatch.StartNew();
|
||||
var uri = new HttpUri(url);
|
||||
|
||||
using (var webClient = new GZipWebClient())
|
||||
{
|
||||
webClient.Headers.Add(HttpRequestHeader.UserAgent, _userAgentBuilder.GetUserAgent());
|
||||
webClient.Proxy = GetProxy(uri);
|
||||
webClient.DownloadFile(uri.FullUri, fileName);
|
||||
stopWatch.Stop();
|
||||
_logger.Debug("Downloading Completed. took {0:0}s", stopWatch.Elapsed.Seconds);
|
||||
}
|
||||
}
|
||||
catch (WebException e)
|
||||
{
|
||||
_logger.Warn("Failed to get response from: {0} {1}", url, e.Message);
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warn(e, "Failed to get response from: " + url);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual IWebProxy GetProxy(HttpUri uri)
|
||||
{
|
||||
IWebProxy proxy = null;
|
||||
|
||||
var proxySettings = _proxySettingsProvider.GetProxySettings(uri);
|
||||
|
||||
if (proxySettings != null)
|
||||
{
|
||||
handler.Proxy = _createManagedWebProxy.GetWebProxy(proxySettings);
|
||||
proxy = _createManagedWebProxy.GetWebProxy(proxySettings);
|
||||
}
|
||||
|
||||
var client = new System.Net.Http.HttpClient(handler)
|
||||
{
|
||||
Timeout = Timeout.InfiniteTimeSpan
|
||||
};
|
||||
|
||||
return client;
|
||||
return proxy;
|
||||
}
|
||||
|
||||
protected virtual void AddRequestHeaders(HttpRequestMessage webRequest, HttpHeader headers)
|
||||
protected virtual void AddRequestHeaders(HttpWebRequest webRequest, HttpHeader headers)
|
||||
{
|
||||
foreach (var header in headers)
|
||||
{
|
||||
switch (header.Key)
|
||||
{
|
||||
case "Accept":
|
||||
webRequest.Headers.Accept.ParseAdd(header.Value);
|
||||
webRequest.Accept = header.Value;
|
||||
break;
|
||||
case "Connection":
|
||||
webRequest.Headers.Connection.Clear();
|
||||
webRequest.Headers.Connection.Add(header.Value);
|
||||
webRequest.Connection = header.Value;
|
||||
break;
|
||||
case "Content-Length":
|
||||
AddContentHeader(webRequest, "Content-Length", header.Value);
|
||||
webRequest.ContentLength = Convert.ToInt64(header.Value);
|
||||
break;
|
||||
case "Content-Type":
|
||||
AddContentHeader(webRequest, "Content-Type", header.Value);
|
||||
webRequest.ContentType = header.Value;
|
||||
break;
|
||||
case "Date":
|
||||
webRequest.Headers.Remove("Date");
|
||||
webRequest.Headers.Date = HttpHeader.ParseDateTime(header.Value);
|
||||
webRequest.Date = HttpHeader.ParseDateTime(header.Value);
|
||||
break;
|
||||
case "Expect":
|
||||
webRequest.Headers.Expect.ParseAdd(header.Value);
|
||||
webRequest.Expect = header.Value;
|
||||
break;
|
||||
case "Host":
|
||||
webRequest.Headers.Host = header.Value;
|
||||
webRequest.Host = header.Value;
|
||||
break;
|
||||
case "If-Modified-Since":
|
||||
webRequest.Headers.IfModifiedSince = HttpHeader.ParseDateTime(header.Value);
|
||||
webRequest.IfModifiedSince = HttpHeader.ParseDateTime(header.Value);
|
||||
break;
|
||||
case "Range":
|
||||
throw new NotImplementedException();
|
||||
case "Referer":
|
||||
webRequest.Headers.Add("Referer", header.Value);
|
||||
webRequest.Referer = header.Value;
|
||||
break;
|
||||
case "Transfer-Encoding":
|
||||
webRequest.Headers.TransferEncoding.ParseAdd(header.Value);
|
||||
webRequest.TransferEncoding = header.Value;
|
||||
break;
|
||||
case "User-Agent":
|
||||
throw new NotSupportedException("User-Agent other than Radarr not allowed.");
|
||||
@@ -211,79 +219,5 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddContentHeader(HttpRequestMessage request, string header, string value)
|
||||
{
|
||||
var headers = request.Content?.Headers;
|
||||
if (headers == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
headers.Remove(header);
|
||||
headers.Add(header, value);
|
||||
}
|
||||
|
||||
private static async ValueTask<Stream> onConnect(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
// Until .NET supports an implementation of Happy Eyeballs (https://tools.ietf.org/html/rfc8305#section-2), let's make IPv4 fallback work in a simple way.
|
||||
// This issue is being tracked at https://github.com/dotnet/runtime/issues/26177 and expected to be fixed in .NET 6.
|
||||
if (useIPv6)
|
||||
{
|
||||
try
|
||||
{
|
||||
var localToken = cancellationToken;
|
||||
|
||||
if (!hasResolvedIPv6Availability)
|
||||
{
|
||||
// to make things move fast, use a very low timeout for the initial ipv6 attempt.
|
||||
var quickFailCts = new CancellationTokenSource(connection_establish_timeout);
|
||||
var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, quickFailCts.Token);
|
||||
|
||||
localToken = linkedTokenSource.Token;
|
||||
}
|
||||
|
||||
return await attemptConnection(AddressFamily.InterNetworkV6, context, localToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// very naively fallback to ipv4 permanently for this execution based on the response of the first connection attempt.
|
||||
// note that this may cause users to eventually get switched to ipv4 (on a random failure when they are switching networks, for instance)
|
||||
// but in the interest of keeping this implementation simple, this is acceptable.
|
||||
useIPv6 = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
hasResolvedIPv6Availability = true;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to IPv4.
|
||||
return await attemptConnection(AddressFamily.InterNetwork, context, cancellationToken);
|
||||
}
|
||||
|
||||
private static async ValueTask<Stream> attemptConnection(AddressFamily addressFamily, SocketsHttpConnectionContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
// The following socket constructor will create a dual-mode socket on systems where IPV6 is available.
|
||||
var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp)
|
||||
{
|
||||
// Turn off Nagle's algorithm since it degrades performance in most HttpClient scenarios.
|
||||
NoDelay = true
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
await socket.ConnectAsync(context.DnsEndPoint, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// The stream should take the ownership of the underlying socket,
|
||||
// closing it when it's disposed.
|
||||
return new NetworkStream(socket, ownsSocket: true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
socket.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
src/NzbDrone.Common/Http/GZipWebClient.cs
Normal file
15
src/NzbDrone.Common/Http/GZipWebClient.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace NzbDrone.Common.Http
|
||||
{
|
||||
public class GZipWebClient : WebClient
|
||||
{
|
||||
protected override WebRequest GetWebRequest(Uri address)
|
||||
{
|
||||
var request = (HttpWebRequest)base.GetWebRequest(address);
|
||||
request.AutomaticDecompression = DecompressionMethods.GZip;
|
||||
return request;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
@@ -121,6 +119,8 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
var stopWatch = Stopwatch.StartNew();
|
||||
|
||||
PrepareRequestCookies(request, cookieContainer);
|
||||
|
||||
var response = _httpDispatcher.GetResponse(request, cookieContainer);
|
||||
|
||||
HandleResponseCookies(response, cookieContainer);
|
||||
@@ -187,98 +187,57 @@ namespace NzbDrone.Common.Http
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleResponseCookies(HttpResponse response, CookieContainer container)
|
||||
private void PrepareRequestCookies(HttpRequest request, CookieContainer cookieContainer)
|
||||
{
|
||||
foreach (Cookie cookie in container.GetAllCookies())
|
||||
// Don't collect persistnet cookies for intermediate/redirected urls.
|
||||
/*lock (_cookieContainerCache)
|
||||
{
|
||||
cookie.Expired = true;
|
||||
}
|
||||
var presistentContainer = _cookieContainerCache.Get("container", () => new CookieContainer());
|
||||
var persistentCookies = presistentContainer.GetCookies((Uri)request.Url);
|
||||
var existingCookies = cookieContainer.GetCookies((Uri)request.Url);
|
||||
|
||||
cookieContainer.Add(persistentCookies);
|
||||
cookieContainer.Add(existingCookies);
|
||||
}*/
|
||||
}
|
||||
|
||||
private void HandleResponseCookies(HttpResponse response, CookieContainer cookieContainer)
|
||||
{
|
||||
var cookieHeaders = response.GetCookieHeaders();
|
||||
|
||||
if (cookieHeaders.Empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AddCookiesToContainer(response.Request.Url, cookieHeaders, container);
|
||||
|
||||
if (response.Request.StoreResponseCookie)
|
||||
{
|
||||
lock (_cookieContainerCache)
|
||||
{
|
||||
var persistentCookieContainer = _cookieContainerCache.Get("container", () => new CookieContainer());
|
||||
|
||||
AddCookiesToContainer(response.Request.Url, cookieHeaders, persistentCookieContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddCookiesToContainer(HttpUri url, string[] cookieHeaders, CookieContainer container)
|
||||
{
|
||||
foreach (var cookieHeader in cookieHeaders)
|
||||
{
|
||||
try
|
||||
{
|
||||
container.SetCookies((Uri)url, cookieHeader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Debug(ex, "Invalid cookie in {0}", url);
|
||||
foreach (var cookieHeader in cookieHeaders)
|
||||
{
|
||||
try
|
||||
{
|
||||
persistentCookieContainer.SetCookies((Uri)response.Request.Url, cookieHeader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Debug(ex, "Invalid cookie in {0}", response.Request.Url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DownloadFile(string url, string fileName)
|
||||
{
|
||||
var fileNamePart = fileName + ".part";
|
||||
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(fileName);
|
||||
if (fileInfo.Directory != null && !fileInfo.Directory.Exists)
|
||||
{
|
||||
fileInfo.Directory.Create();
|
||||
}
|
||||
|
||||
_logger.Debug("Downloading [{0}] to [{1}]", url, fileName);
|
||||
|
||||
var stopWatch = Stopwatch.StartNew();
|
||||
using (var fileStream = new FileStream(fileNamePart, FileMode.Create, FileAccess.ReadWrite))
|
||||
{
|
||||
var request = new HttpRequest(url);
|
||||
request.AllowAutoRedirect = true;
|
||||
request.ResponseStream = fileStream;
|
||||
var response = Get(request);
|
||||
|
||||
if (response.Headers.ContentType != null && response.Headers.ContentType.Contains("text/html"))
|
||||
{
|
||||
throw new HttpException(request, response, "Site responded with html content.");
|
||||
}
|
||||
}
|
||||
|
||||
stopWatch.Stop();
|
||||
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
File.Delete(fileName);
|
||||
}
|
||||
|
||||
File.Move(fileNamePart, fileName);
|
||||
_logger.Debug("Downloading Completed. took {0:0}s", stopWatch.Elapsed.Seconds);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(fileNamePart))
|
||||
{
|
||||
File.Delete(fileNamePart);
|
||||
}
|
||||
}
|
||||
_httpDispatcher.DownloadFile(url, fileName);
|
||||
}
|
||||
|
||||
public HttpResponse Get(HttpRequest request)
|
||||
{
|
||||
request.Method = HttpMethod.Get;
|
||||
request.Method = HttpMethod.GET;
|
||||
return Execute(request);
|
||||
}
|
||||
|
||||
@@ -292,13 +251,13 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
public HttpResponse Head(HttpRequest request)
|
||||
{
|
||||
request.Method = HttpMethod.Head;
|
||||
request.Method = HttpMethod.HEAD;
|
||||
return Execute(request);
|
||||
}
|
||||
|
||||
public HttpResponse Post(HttpRequest request)
|
||||
{
|
||||
request.Method = HttpMethod.Post;
|
||||
request.Method = HttpMethod.POST;
|
||||
return Execute(request);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +1,14 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Common.Http
|
||||
{
|
||||
public static class WebHeaderCollectionExtensions
|
||||
{
|
||||
public static NameValueCollection ToNameValueCollection(this HttpHeaders headers)
|
||||
{
|
||||
var result = new NameValueCollection();
|
||||
foreach (var header in headers)
|
||||
{
|
||||
result.Add(header.Key, header.Value.ConcatToString(";"));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public class HttpHeader : NameValueCollection, IEnumerable<KeyValuePair<string, string>>, IEnumerable
|
||||
{
|
||||
public HttpHeader(NameValueCollection headers)
|
||||
@@ -32,11 +16,6 @@ namespace NzbDrone.Common.Http
|
||||
{
|
||||
}
|
||||
|
||||
public HttpHeader(HttpHeaders headers)
|
||||
: base(headers.ToNameValueCollection())
|
||||
{
|
||||
}
|
||||
|
||||
public HttpHeader()
|
||||
{
|
||||
}
|
||||
|
||||
14
src/NzbDrone.Common/Http/HttpMethod.cs
Normal file
14
src/NzbDrone.Common/Http/HttpMethod.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace NzbDrone.Common.Http
|
||||
{
|
||||
public enum HttpMethod
|
||||
{
|
||||
GET,
|
||||
POST,
|
||||
PUT,
|
||||
DELETE,
|
||||
HEAD,
|
||||
OPTIONS,
|
||||
PATCH,
|
||||
MERGE
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
@@ -13,7 +11,6 @@ namespace NzbDrone.Common.Http
|
||||
{
|
||||
public HttpRequest(string url, HttpAccept httpAccept = null)
|
||||
{
|
||||
Method = HttpMethod.Get;
|
||||
Url = new HttpUri(url);
|
||||
Headers = new HttpHeader();
|
||||
AllowAutoRedirect = true;
|
||||
@@ -52,7 +49,6 @@ namespace NzbDrone.Common.Http
|
||||
public TimeSpan RequestTimeout { get; set; }
|
||||
public TimeSpan RateLimit { get; set; }
|
||||
public string RateLimitKey { get; set; }
|
||||
public Stream ResponseStream { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
@@ -36,7 +35,7 @@ namespace NzbDrone.Common.Http
|
||||
{
|
||||
BaseUrl = new HttpUri(baseUrl);
|
||||
ResourceUrl = string.Empty;
|
||||
Method = HttpMethod.Get;
|
||||
Method = HttpMethod.GET;
|
||||
QueryParams = new List<KeyValuePair<string, string>>();
|
||||
SuffixQueryParams = new List<KeyValuePair<string, string>>();
|
||||
Segments = new Dictionary<string, string>();
|
||||
@@ -272,7 +271,7 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
public virtual HttpRequestBuilder Post()
|
||||
{
|
||||
Method = HttpMethod.Post;
|
||||
Method = HttpMethod.POST;
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -363,7 +362,7 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
public virtual HttpRequestBuilder AddFormParameter(string key, object value)
|
||||
{
|
||||
if (Method != HttpMethod.Post)
|
||||
if (Method != HttpMethod.POST)
|
||||
{
|
||||
throw new NotSupportedException("HttpRequest Method must be POST to add FormParameter.");
|
||||
}
|
||||
@@ -379,7 +378,7 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
public virtual HttpRequestBuilder AddFormUpload(string name, string fileName, byte[] data, string contentType = "application/octet-stream")
|
||||
{
|
||||
if (Method != HttpMethod.Post)
|
||||
if (Method != HttpMethod.POST)
|
||||
{
|
||||
throw new NotSupportedException("HttpRequest Method must be POST to add FormUpload.");
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Serializer;
|
||||
|
||||
@@ -18,14 +17,14 @@ namespace NzbDrone.Common.Http
|
||||
public JsonRpcRequestBuilder(string baseUrl)
|
||||
: base(baseUrl)
|
||||
{
|
||||
Method = HttpMethod.Post;
|
||||
Method = HttpMethod.POST;
|
||||
JsonParameters = new List<object>();
|
||||
}
|
||||
|
||||
public JsonRpcRequestBuilder(string baseUrl, string method, IEnumerable<object> parameters)
|
||||
: base(baseUrl)
|
||||
{
|
||||
Method = HttpMethod.Post;
|
||||
Method = HttpMethod.POST;
|
||||
JsonMethod = method;
|
||||
JsonParameters = parameters.ToList();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using com.LandonKey.SocksWebProxy;
|
||||
using com.LandonKey.SocksWebProxy.Proxy;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
@@ -29,37 +33,54 @@ namespace NzbDrone.Common.Http.Proxy
|
||||
}
|
||||
|
||||
private IWebProxy CreateWebProxy(HttpProxySettings proxySettings)
|
||||
{
|
||||
var uri = GetProxyUri(proxySettings);
|
||||
|
||||
if (uri == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (proxySettings.Username.IsNotNullOrWhiteSpace() && proxySettings.Password.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
return new WebProxy(uri, proxySettings.BypassLocalAddress, proxySettings.BypassListAsArray, new NetworkCredential(proxySettings.Username, proxySettings.Password));
|
||||
}
|
||||
else
|
||||
{
|
||||
return new WebProxy(uri, proxySettings.BypassLocalAddress, proxySettings.BypassListAsArray);
|
||||
}
|
||||
}
|
||||
|
||||
private Uri GetProxyUri(HttpProxySettings proxySettings)
|
||||
{
|
||||
switch (proxySettings.Type)
|
||||
{
|
||||
case ProxyType.Http:
|
||||
return new Uri("http://" + proxySettings.Host + ":" + proxySettings.Port);
|
||||
if (proxySettings.Username.IsNotNullOrWhiteSpace() && proxySettings.Password.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
return new WebProxy(proxySettings.Host + ":" + proxySettings.Port, proxySettings.BypassLocalAddress, proxySettings.BypassListAsArray, new NetworkCredential(proxySettings.Username, proxySettings.Password));
|
||||
}
|
||||
else
|
||||
{
|
||||
return new WebProxy(proxySettings.Host + ":" + proxySettings.Port, proxySettings.BypassLocalAddress, proxySettings.BypassListAsArray);
|
||||
}
|
||||
|
||||
case ProxyType.Socks4:
|
||||
return new Uri("socks4://" + proxySettings.Host + ":" + proxySettings.Port);
|
||||
return new SocksWebProxy(new ProxyConfig(IPAddress.Loopback, GetNextFreePort(), GetProxyIpAddress(proxySettings.Host), proxySettings.Port, ProxyConfig.SocksVersion.Four, proxySettings.Username, proxySettings.Password), false);
|
||||
case ProxyType.Socks5:
|
||||
return new Uri("socks5://" + proxySettings.Host + ":" + proxySettings.Port);
|
||||
default:
|
||||
return null;
|
||||
return new SocksWebProxy(new ProxyConfig(IPAddress.Loopback, GetNextFreePort(), GetProxyIpAddress(proxySettings.Host), proxySettings.Port, ProxyConfig.SocksVersion.Five, proxySettings.Username, proxySettings.Password), false);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static IPAddress GetProxyIpAddress(string host)
|
||||
{
|
||||
IPAddress ipAddress;
|
||||
if (!IPAddress.TryParse(host, out ipAddress))
|
||||
{
|
||||
try
|
||||
{
|
||||
ipAddress = Dns.GetHostEntry(host).AddressList.OrderByDescending(a => a.AddressFamily == AddressFamily.InterNetwork).First();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidOperationException(string.Format("Unable to resolve proxy hostname '{0}' to a valid IP address.", host), e);
|
||||
}
|
||||
}
|
||||
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
private static int GetNextFreePort()
|
||||
{
|
||||
var listener = new TcpListener(IPAddress.Loopback, 0);
|
||||
listener.Start();
|
||||
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
|
||||
listener.Stop();
|
||||
|
||||
return port;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
private static readonly Regex[] CleansingRules = new[]
|
||||
{
|
||||
// Url
|
||||
new Regex(@"(?<=\?|&|: )(apikey|(?:access[-_]?)?token|passkey|auth|authkey|user|uid|api|[a-z_]*apikey|account|passwd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=\?|&|: )(apikey|token|passkey|auth|authkey|user|uid|api|[a-z_]*apikey|account|passwd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=\?|&)[^=]*?(username|password)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net5.0</TargetFrameworks>
|
||||
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DryIoc.dll" Version="4.8.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="NLog" Version="4.7.12" />
|
||||
<PackageReference Include="DryIoc.dll" Version="4.7.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
|
||||
<PackageReference Include="DotNet4.SocksProxy" Version="1.4.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="NLog" Version="4.7.11" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" />
|
||||
<PackageReference Include="Sentry" Version="3.11.1" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.0" />
|
||||
<PackageReference Include="Sentry" Version="3.9.3" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.2.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="5.0.2" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.113.0-0" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="5.0.0" />
|
||||
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
|
||||
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="6.0.0" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net5.0</TargetFrameworks>
|
||||
|
||||
<ApplicationIcon>..\NzbDrone.Host\Radarr.ico</ApplicationIcon>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="!$(RuntimeIdentifier.StartsWith('win'))">
|
||||
<AssemblyName>Radarr</AssemblyName>
|
||||
|
||||
14
src/NzbDrone.Console/app.config
Normal file
14
src/NzbDrone.Console/app.config
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<system.net>
|
||||
<connectionManagement>
|
||||
<add address="*" maxconnection="100" />
|
||||
</connectionManagement>
|
||||
</system.net>
|
||||
<startup useLegacyV2RuntimeActivationPolicy="true">
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
|
||||
</startup>
|
||||
<runtime>
|
||||
<loadFromRemoteSources enabled="true" />
|
||||
</runtime>
|
||||
</configuration>
|
||||
31
src/NzbDrone.Console/app.manifest
Normal file
31
src/NzbDrone.Console/app.manifest
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
|
||||
<!-- Windows 8 -->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
|
||||
|
||||
<!-- Windows 8.1 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
|
||||
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -1,131 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Datastore.Migration;
|
||||
using NzbDrone.Core.Download.Clients.RTorrent;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.Datastore.Migration
|
||||
{
|
||||
[TestFixture]
|
||||
public class cdh_per_downloadclientFixture : MigrationTest<cdh_per_downloadclient>
|
||||
{
|
||||
[Test]
|
||||
public void should_set_cdh_to_enabled()
|
||||
{
|
||||
var db = WithMigrationTestDb(c =>
|
||||
{
|
||||
c.Insert.IntoTable("DownloadClients").Row(new
|
||||
{
|
||||
Enable = 1,
|
||||
Name = "Deluge",
|
||||
Implementation = "Deluge",
|
||||
Priority = 1,
|
||||
Settings = new DelugeSettings85
|
||||
{
|
||||
Host = "127.0.0.1",
|
||||
MovieCategory = "abc",
|
||||
UrlBase = "/my/"
|
||||
}.ToJson(),
|
||||
ConfigContract = "DelugeSettings"
|
||||
});
|
||||
});
|
||||
|
||||
var items = db.Query<DownloadClientDefinition158>("SELECT * FROM DownloadClients");
|
||||
|
||||
items.Should().HaveCount(1);
|
||||
items.First().RemoveCompletedDownloads.Should().BeFalse();
|
||||
items.First().RemoveFailedDownloads.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_set_cdh_to_disabled_when_globally_disabled()
|
||||
{
|
||||
var db = WithMigrationTestDb(c =>
|
||||
{
|
||||
c.Insert.IntoTable("Config").Row(new
|
||||
{
|
||||
Key = "removecompleteddownloads",
|
||||
Value = "True"
|
||||
});
|
||||
|
||||
c.Insert.IntoTable("DownloadClients").Row(new
|
||||
{
|
||||
Enable = 1,
|
||||
Name = "Deluge",
|
||||
Implementation = "Deluge",
|
||||
Priority = 1,
|
||||
Settings = new DelugeSettings85
|
||||
{
|
||||
Host = "127.0.0.1",
|
||||
MovieCategory = "abc",
|
||||
UrlBase = "/my/"
|
||||
}.ToJson(),
|
||||
ConfigContract = "DelugeSettings"
|
||||
});
|
||||
});
|
||||
|
||||
var items = db.Query<DownloadClientDefinition158>("SELECT * FROM DownloadClients");
|
||||
|
||||
items.Should().HaveCount(1);
|
||||
items.First().RemoveCompletedDownloads.Should().BeTrue();
|
||||
items.First().RemoveFailedDownloads.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_disable_remove_for_existing_rtorrent()
|
||||
{
|
||||
var db = WithMigrationTestDb(c =>
|
||||
{
|
||||
c.Insert.IntoTable("DownloadClients").Row(new
|
||||
{
|
||||
Enable = 1,
|
||||
Name = "RTorrent",
|
||||
Implementation = "RTorrent",
|
||||
Priority = 1,
|
||||
Settings = new RTorrentSettings
|
||||
{
|
||||
Host = "127.0.0.1",
|
||||
MovieCategory = "abc",
|
||||
UrlBase = "/my/"
|
||||
}.ToJson(),
|
||||
ConfigContract = "RTorrentSettings"
|
||||
});
|
||||
});
|
||||
|
||||
var items = db.Query<DownloadClientDefinition158>("SELECT * FROM DownloadClients");
|
||||
|
||||
items.Should().HaveCount(1);
|
||||
items.First().RemoveCompletedDownloads.Should().BeFalse();
|
||||
items.First().RemoveFailedDownloads.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
|
||||
public class DownloadClientDefinition158
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public bool Enable { get; set; }
|
||||
public int Priority { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Implementation { get; set; }
|
||||
public JsonElement Settings { get; set; }
|
||||
public string ConfigContract { get; set; }
|
||||
public bool RemoveCompletedDownloads { get; set; }
|
||||
public bool RemoveFailedDownloads { get; set; }
|
||||
}
|
||||
|
||||
public class DelugeSettings85
|
||||
{
|
||||
public string Host { get; set; }
|
||||
public int Port { get; set; }
|
||||
public string UrlBase { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string MovieCategory { get; set; }
|
||||
public int RecentTvPriority { get; set; }
|
||||
public int OlderTvPriority { get; set; }
|
||||
public bool UseSsl { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||
{
|
||||
var remoteMovie = new RemoteMovie();
|
||||
remoteMovie.ParsedMovieInfo = new ParsedMovieInfo();
|
||||
remoteMovie.ParsedMovieInfo.MovieTitles = new List<string> { "A Movie" };
|
||||
remoteMovie.ParsedMovieInfo.MovieTitle = "A Movie";
|
||||
remoteMovie.ParsedMovieInfo.Year = 1998;
|
||||
remoteMovie.ParsedMovieInfo.Quality = quality;
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.DecisionEngine.Specifications.RssSync;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
@@ -32,13 +31,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
|
||||
};
|
||||
|
||||
Mocker
|
||||
.GetMock<IIndexerFactory>()
|
||||
.GetMock<IIndexerRepository>()
|
||||
.Setup(m => m.Get(It.IsAny<int>()))
|
||||
.Throws(new ModelNotFoundException(typeof(IndexerDefinition), -1));
|
||||
|
||||
Mocker
|
||||
.GetMock<IIndexerFactory>()
|
||||
.Setup(m => m.Get(1))
|
||||
.Returns(_fakeIndexerDefinition);
|
||||
|
||||
_specification = Mocker.Resolve<IndexerTagSpecification>();
|
||||
@@ -104,25 +98,5 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
|
||||
|
||||
_specification.IsSatisfiedBy(_parseResultMulti, new MovieSearchCriteria()).Accepted.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void release_without_indexerid_should_return_true()
|
||||
{
|
||||
_fakeIndexerDefinition.Tags = new HashSet<int> { 456 };
|
||||
_fakeMovie.Tags = new HashSet<int> { 123, 789 };
|
||||
_fakeRelease.IndexerId = 0;
|
||||
|
||||
_specification.IsSatisfiedBy(_parseResultMulti, new MovieSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void release_with_invalid_indexerid_should_return_true()
|
||||
{
|
||||
_fakeIndexerDefinition.Tags = new HashSet<int> { 456 };
|
||||
_fakeMovie.Tags = new HashSet<int> { 123, 789 };
|
||||
_fakeRelease.IndexerId = 2;
|
||||
|
||||
_specification.IsSatisfiedBy(_parseResultMulti, new MovieSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
{
|
||||
Quality = quality,
|
||||
Year = 1998,
|
||||
MovieTitles = new List<string> { "A Movie" },
|
||||
MovieTitle = "A Movie",
|
||||
},
|
||||
Movie = movie,
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
_pending.Add(new PendingRelease
|
||||
{
|
||||
Id = id,
|
||||
ParsedMovieInfo = new ParsedMovieInfo { MovieTitles = new List<string> { title }, Year = year },
|
||||
ParsedMovieInfo = new ParsedMovieInfo { MovieTitle = title, Year = year },
|
||||
MovieId = _movie.Id
|
||||
});
|
||||
}
|
||||
|
||||
@@ -49,13 +49,13 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
|
||||
|
||||
ParsedMovieInfo = new ParsedMovieInfo()
|
||||
{
|
||||
MovieTitles = new List<string> { "A Movie" },
|
||||
MovieTitle = "A Movie",
|
||||
Year = 1998
|
||||
}
|
||||
};
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(s => s.Map(It.Is<ParsedMovieInfo>(i => i.PrimaryMovieTitle == "A Movie"), It.IsAny<string>(), null))
|
||||
.Setup(s => s.Map(It.Is<ParsedMovieInfo>(i => i.MovieTitle == "A Movie"), It.IsAny<string>(), null))
|
||||
.Returns(new MappingResult { RemoteMovie = remoteEpisode });
|
||||
|
||||
ParseMovieTitle();
|
||||
@@ -97,7 +97,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
|
||||
|
||||
ParsedMovieInfo = new ParsedMovieInfo()
|
||||
{
|
||||
MovieTitles = { "A Movie" },
|
||||
MovieTitle = "A Movie",
|
||||
Year = 1998
|
||||
}
|
||||
};
|
||||
@@ -156,7 +156,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
|
||||
|
||||
ParsedMovieInfo = new ParsedMovieInfo()
|
||||
{
|
||||
MovieTitles = { "A Movie" },
|
||||
MovieTitle = "A Movie",
|
||||
Year = 1998
|
||||
}
|
||||
};
|
||||
|
||||
@@ -11,7 +11,6 @@ using NzbDrone.Common.TPL;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Http;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Security;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.Framework
|
||||
@@ -26,8 +25,7 @@ namespace NzbDrone.Core.Test.Framework
|
||||
|
||||
Mocker.SetConstant<IHttpProxySettingsProvider>(new HttpProxySettingsProvider(Mocker.Resolve<ConfigService>()));
|
||||
Mocker.SetConstant<ICreateManagedWebProxy>(new ManagedWebProxyFactory(Mocker.Resolve<CacheManager>()));
|
||||
Mocker.SetConstant<ICertificateValidationService>(new X509CertificateValidationService(Mocker.Resolve<ConfigService>(), TestLogger));
|
||||
Mocker.SetConstant<IHttpDispatcher>(new ManagedHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<ICreateManagedWebProxy>(), Mocker.Resolve<ICertificateValidationService>(), Mocker.Resolve<UserAgentBuilder>(), Mocker.Resolve<CacheManager>(), TestLogger));
|
||||
Mocker.SetConstant<IHttpDispatcher>(new ManagedHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<ICreateManagedWebProxy>(), Mocker.Resolve<UserAgentBuilder>(), Mocker.Resolve<IPlatformInfo>(), TestLogger));
|
||||
Mocker.SetConstant<IHttpClient>(new HttpClient(Array.Empty<IHttpRequestInterceptor>(), Mocker.Resolve<CacheManager>(), Mocker.Resolve<RateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), TestLogger));
|
||||
Mocker.SetConstant<IRadarrCloudRequestBuilder>(new RadarrCloudRequestBuilder());
|
||||
}
|
||||
|
||||
@@ -70,5 +70,84 @@ namespace NzbDrone.Core.Test.HistoryTests
|
||||
|
||||
downloadHistory.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_get_movie_history()
|
||||
{
|
||||
var historyMovie1 = Builder<MovieHistory>.CreateNew()
|
||||
.With(c => c.Quality = new QualityModel(Quality.Bluray1080p))
|
||||
.With(c => c.Languages = new List<Language> { Language.English })
|
||||
.With(c => c.MovieId = 12)
|
||||
.With(c => c.EventType = MovieHistoryEventType.Grabbed)
|
||||
.BuildNew();
|
||||
|
||||
var historyMovie2 = Builder<MovieHistory>.CreateNew()
|
||||
.With(c => c.Quality = new QualityModel(Quality.Bluray1080p))
|
||||
.With(c => c.Languages = new List<Language> { Language.English })
|
||||
.With(c => c.MovieId = 13)
|
||||
.With(c => c.EventType = MovieHistoryEventType.Grabbed)
|
||||
.BuildNew();
|
||||
|
||||
Subject.Insert(historyMovie1);
|
||||
Subject.Insert(historyMovie2);
|
||||
|
||||
var movieHistory = Subject.GetByMovieId(12, null);
|
||||
|
||||
movieHistory.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_sort_movie_history_by_date()
|
||||
{
|
||||
var historyFirst = Builder<MovieHistory>.CreateNew()
|
||||
.With(c => c.Quality = new QualityModel(Quality.Bluray1080p))
|
||||
.With(c => c.Languages = new List<Language> { Language.English })
|
||||
.With(c => c.MovieId = 12)
|
||||
.With(c => c.EventType = MovieHistoryEventType.MovieFileRenamed)
|
||||
.With(c => c.Date = DateTime.UtcNow)
|
||||
.BuildNew();
|
||||
|
||||
var historySecond = Builder<MovieHistory>.CreateNew()
|
||||
.With(c => c.Quality = new QualityModel(Quality.Bluray1080p))
|
||||
.With(c => c.Languages = new List<Language> { Language.English })
|
||||
.With(c => c.MovieId = 12)
|
||||
.With(c => c.EventType = MovieHistoryEventType.Grabbed)
|
||||
.With(c => c.Date = DateTime.UtcNow.AddMinutes(10))
|
||||
.BuildNew();
|
||||
|
||||
Subject.Insert(historyFirst);
|
||||
Subject.Insert(historySecond);
|
||||
|
||||
var movieHistory = Subject.GetByMovieId(12, null);
|
||||
|
||||
movieHistory.Should().HaveCount(2);
|
||||
movieHistory.First().EventType.Should().Be(MovieHistoryEventType.Grabbed);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_delete_history_items_by_movieId()
|
||||
{
|
||||
var items = Builder<MovieHistory>.CreateListOfSize(5)
|
||||
.TheFirst(1)
|
||||
.With(c => c.MovieId = _movie2.Id)
|
||||
.TheRest()
|
||||
.With(c => c.MovieId = _movie1.Id)
|
||||
.All()
|
||||
.With(c => c.Id = 0)
|
||||
.With(c => c.Quality = new QualityModel(Quality.Bluray1080p))
|
||||
.With(c => c.Languages = new List<Language> { Language.English })
|
||||
.With(c => c.EventType = MovieHistoryEventType.Grabbed)
|
||||
.BuildListOfNew();
|
||||
|
||||
Db.InsertMany(items);
|
||||
|
||||
Subject.DeleteForMovies(new List<int> { _movie1.Id });
|
||||
|
||||
var removedItems = Subject.GetByMovieId(_movie1.Id, null);
|
||||
var nonRemovedItems = Subject.GetByMovieId(_movie2.Id, null);
|
||||
|
||||
removedItems.Should().HaveCount(0);
|
||||
nonRemovedItems.Should().HaveCount(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.IndexerSearch;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.Movies.Translations;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
{
|
||||
public class ReleaseSearchServiceFixture : CoreTest<ReleaseSearchService>
|
||||
{
|
||||
private Mock<IIndexer> _mockIndexer;
|
||||
private Movie _movie;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_mockIndexer = Mocker.GetMock<IIndexer>();
|
||||
_mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition { Id = 1 });
|
||||
_mockIndexer.SetupGet(s => s.SupportsSearch).Returns(true);
|
||||
|
||||
Mocker.GetMock<IIndexerFactory>()
|
||||
.Setup(s => s.AutomaticSearchEnabled(true))
|
||||
.Returns(new List<IIndexer> { _mockIndexer.Object });
|
||||
|
||||
Mocker.GetMock<IMakeDownloadDecision>()
|
||||
.Setup(s => s.GetSearchDecision(It.IsAny<List<Parser.Model.ReleaseInfo>>(), It.IsAny<SearchCriteriaBase>()))
|
||||
.Returns(new List<DownloadDecision>());
|
||||
|
||||
_movie = Builder<Movie>.CreateNew()
|
||||
.With(v => v.Monitored = true)
|
||||
.Build();
|
||||
|
||||
Mocker.GetMock<IMovieService>()
|
||||
.Setup(v => v.GetMovie(_movie.Id))
|
||||
.Returns(_movie);
|
||||
|
||||
Mocker.GetMock<IMovieTranslationService>()
|
||||
.Setup(s => s.GetAllTranslationsForMovie(It.IsAny<int>()))
|
||||
.Returns(new List<MovieTranslation>());
|
||||
}
|
||||
|
||||
private List<SearchCriteriaBase> WatchForSearchCriteria()
|
||||
{
|
||||
var result = new List<SearchCriteriaBase>();
|
||||
|
||||
_mockIndexer.Setup(v => v.Fetch(It.IsAny<MovieSearchCriteria>()))
|
||||
.Callback<MovieSearchCriteria>(s => result.Add(s))
|
||||
.Returns(new List<Parser.Model.ReleaseInfo>());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Tags_IndexerTags_MovieNoTags_IndexerNotIncluded()
|
||||
{
|
||||
_mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition
|
||||
{
|
||||
Id = 1,
|
||||
Tags = new HashSet<int> { 3 }
|
||||
});
|
||||
|
||||
var allCriteria = WatchForSearchCriteria();
|
||||
|
||||
Subject.MovieSearch(_movie, true, false);
|
||||
|
||||
var criteria = allCriteria.OfType<MovieSearchCriteria>().ToList();
|
||||
|
||||
criteria.Count.Should().Be(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Tags_IndexerNoTags_MovieTags_IndexerIncluded()
|
||||
{
|
||||
_mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition
|
||||
{
|
||||
Id = 1
|
||||
});
|
||||
|
||||
_movie = Builder<Movie>.CreateNew()
|
||||
.With(v => v.Monitored = true)
|
||||
.With(v => v.Tags = new HashSet<int> { 3 })
|
||||
.Build();
|
||||
|
||||
Mocker.GetMock<IMovieService>()
|
||||
.Setup(v => v.GetMovie(_movie.Id))
|
||||
.Returns(_movie);
|
||||
|
||||
var allCriteria = WatchForSearchCriteria();
|
||||
|
||||
Subject.MovieSearch(_movie, true, false);
|
||||
|
||||
var criteria = allCriteria.OfType<MovieSearchCriteria>().ToList();
|
||||
|
||||
criteria.Count.Should().Be(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Tags_IndexerAndMovieTagsMatch_IndexerIncluded()
|
||||
{
|
||||
_mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition
|
||||
{
|
||||
Id = 1,
|
||||
Tags = new HashSet<int> { 1, 2, 3 }
|
||||
});
|
||||
|
||||
_movie = Builder<Movie>.CreateNew()
|
||||
.With(v => v.Monitored = true)
|
||||
.With(v => v.Tags = new HashSet<int> { 3, 4, 5 })
|
||||
.Build();
|
||||
|
||||
Mocker.GetMock<IMovieService>()
|
||||
.Setup(v => v.GetMovie(_movie.Id))
|
||||
.Returns(_movie);
|
||||
|
||||
var allCriteria = WatchForSearchCriteria();
|
||||
|
||||
Subject.MovieSearch(_movie, true, false);
|
||||
|
||||
var criteria = allCriteria.OfType<MovieSearchCriteria>().ToList();
|
||||
|
||||
criteria.Count.Should().Be(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Tags_IndexerAndMovieTagsMismatch_IndexerNotIncluded()
|
||||
{
|
||||
_mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition
|
||||
{
|
||||
Id = 1,
|
||||
Tags = new HashSet<int> { 1, 2, 3 }
|
||||
});
|
||||
|
||||
_movie = Builder<Movie>.CreateNew()
|
||||
.With(v => v.Monitored = true)
|
||||
.With(v => v.Tags = new HashSet<int> { 4, 5, 6 })
|
||||
.Build();
|
||||
|
||||
Mocker.GetMock<IMovieService>()
|
||||
.Setup(v => v.GetMovie(_movie.Id))
|
||||
.Returns(_movie);
|
||||
|
||||
var allCriteria = WatchForSearchCriteria();
|
||||
|
||||
Subject.MovieSearch(_movie, true, false);
|
||||
|
||||
var criteria = allCriteria.OfType<MovieSearchCriteria>().ToList();
|
||||
|
||||
criteria.Count.Should().Be(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
public void should_replace_some_special_characters(string input, string expected)
|
||||
{
|
||||
Subject.SceneTitles = new List<string> { input };
|
||||
Subject.CleanSceneTitles.First().Should().Be(expected);
|
||||
Subject.QueryTitles.First().Should().Be(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -31,7 +30,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/FileList/RecentFeed.json");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
@@ -35,7 +34,7 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
|
||||
var responseJson = ReadAllText(fileName);
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Post)))
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.POST)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), responseJson));
|
||||
|
||||
var torrents = Subject.FetchRecent();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -89,7 +88,7 @@ namespace NzbDrone.Core.Test.IndexerTests.IPTorrentsTests
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/IPTorrents/IPTorrents.xml");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -42,7 +41,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Newznab/newznab_nzb_su.xml");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -31,7 +30,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NyaaTests
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Nyaa/Nyaa.xml");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -34,7 +33,7 @@ namespace NzbDrone.Core.Test.IndexerTests.OmgwtfnzbsTests
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Omgwtfnzbs/Omgwtfnzbs.xml");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -35,11 +34,11 @@ namespace NzbDrone.Core.Test.IndexerTests.PTPTests
|
||||
var responseJson = ReadAllText(fileName);
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Post)))
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.POST)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), authStream.ToString()));
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader { ContentType = HttpAccept.Json.Value }, responseJson));
|
||||
|
||||
var torrents = Subject.FetchRecent();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -36,7 +35,7 @@ namespace NzbDrone.Core.Test.IndexerTests.RarbgTests
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Rarbg/RecentFeed_v2.json");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
@@ -63,7 +62,7 @@ namespace NzbDrone.Core.Test.IndexerTests.RarbgTests
|
||||
public void should_parse_error_20_as_empty_results()
|
||||
{
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), "{ error_code: 20, error: \"some message\" }"));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
@@ -75,7 +74,7 @@ namespace NzbDrone.Core.Test.IndexerTests.RarbgTests
|
||||
public void should_warn_on_unknown_error()
|
||||
{
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), "{ error_code: 25, error: \"some message\" }"));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -43,7 +42,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_hdaccess_net.xml");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
@@ -72,7 +71,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_tpb.xml");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
@@ -102,7 +101,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_animetosho.xml");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
|
||||
@@ -45,10 +45,6 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
||||
Mocker.GetMock<IRootFolderService>()
|
||||
.Setup(s => s.GetBestRootFolderPath(It.IsAny<string>()))
|
||||
.Returns(_rootFolder);
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(s => s.GetFilesByMovie(It.IsAny<int>()))
|
||||
.Returns(new List<MovieFile>());
|
||||
}
|
||||
|
||||
private void GivenRootFolder(params string[] subfolders)
|
||||
@@ -121,7 +117,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
||||
.Verify(v => v.Clean(It.IsAny<Movie>(), It.IsAny<List<string>>()), Times.Never());
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.IsAny<List<string>>(), _movie, false), Times.Never());
|
||||
.Verify(v => v.GetImportDecisions(It.IsAny<List<string>>(), _movie), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -156,7 +152,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
||||
Subject.Scan(_movie);
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -181,7 +177,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
||||
Subject.Scan(_movie);
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -201,7 +197,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
||||
Subject.Scan(_movie);
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -233,7 +229,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
||||
.Verify(v => v.Clean(It.IsAny<Movie>(), It.IsAny<List<string>>()), Times.Once());
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.IsAny<List<string>>(), _movie, false), Times.Never());
|
||||
.Verify(v => v.GetImportDecisions(It.IsAny<List<string>>(), _movie), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -251,7 +247,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
||||
Subject.Scan(_movie);
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -274,7 +270,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
||||
Subject.Scan(_movie);
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 4), _movie, false), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 4), _movie), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -293,7 +289,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
||||
Subject.Scan(_movie);
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -313,7 +309,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
||||
Subject.Scan(_movie);
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -330,7 +326,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
||||
Subject.Scan(_movie);
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -347,7 +343,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
||||
Subject.Scan(_movie);
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -366,7 +362,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
||||
Subject.Scan(_movie);
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 2), _movie, false), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 2), _movie), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -383,7 +379,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
||||
Subject.Scan(_movie);
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 2), _movie, false), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 2), _movie), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -401,7 +397,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
||||
Subject.Scan(_movie);
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -418,7 +414,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
||||
Subject.Scan(_movie);
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.MovieImport
|
||||
namespace NzbDrone.Core.Test.MediaFiles
|
||||
{
|
||||
[TestFixture]
|
||||
|
||||
@@ -76,13 +76,6 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
|
||||
_approvedDecisions.ForEach(a => a.LocalMovie.Path = Path.Combine(_downloadClientItem.OutputPath.ToString(), Path.GetFileName(a.LocalMovie.Path)));
|
||||
}
|
||||
|
||||
private void GivenExistingFileOnDisk()
|
||||
{
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(s => s.GetFilesWithRelativePath(It.IsAny<int>(), It.IsAny<string>()))
|
||||
.Returns(new List<MovieFile>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_import_any_if_there_are_no_approved_decisions()
|
||||
{
|
||||
@@ -94,16 +87,12 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
|
||||
[Test]
|
||||
public void should_import_each_approved()
|
||||
{
|
||||
GivenExistingFileOnDisk();
|
||||
|
||||
Subject.Import(_approvedDecisions, false).Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_only_import_approved()
|
||||
{
|
||||
GivenExistingFileOnDisk();
|
||||
|
||||
var all = new List<ImportDecision>();
|
||||
all.AddRange(_rejectedDecisions);
|
||||
all.AddRange(_approvedDecisions);
|
||||
@@ -115,10 +104,8 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_only_import_each_movie_once()
|
||||
public void should_only_import_each_episode_once()
|
||||
{
|
||||
GivenExistingFileOnDisk();
|
||||
|
||||
var all = new List<ImportDecision>();
|
||||
all.AddRange(_approvedDecisions);
|
||||
all.Add(new ImportDecision(_approvedDecisions.First().LocalMovie));
|
||||
@@ -150,8 +137,6 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
|
||||
[Test]
|
||||
public void should_not_move_existing_files()
|
||||
{
|
||||
GivenExistingFileOnDisk();
|
||||
|
||||
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, false);
|
||||
|
||||
Mocker.GetMock<IUpgradeMediaFiles>()
|
||||
@@ -240,8 +225,6 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
|
||||
[Test]
|
||||
public void should_import_larger_files_first()
|
||||
{
|
||||
GivenExistingFileOnDisk();
|
||||
|
||||
var fileDecision = _approvedDecisions.First();
|
||||
fileDecision.LocalMovie.Size = 1.Gigabytes();
|
||||
|
||||
@@ -338,10 +321,10 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
|
||||
{
|
||||
var name = "Transformers.2007.720p.BluRay.x264-Radarr";
|
||||
var outputPath = Path.Combine(@"C:\Test\Unsorted\movies\".AsOsAgnostic(), name);
|
||||
var localMovie = _approvedDecisions.First().LocalMovie;
|
||||
var localEpisode = _approvedDecisions.First().LocalMovie;
|
||||
|
||||
localMovie.FolderMovieInfo = new ParsedMovieInfo { OriginalTitle = name };
|
||||
localMovie.Path = Path.Combine(outputPath, "subfolder", name + ".mkv");
|
||||
localEpisode.FolderMovieInfo = new ParsedMovieInfo { OriginalTitle = name };
|
||||
localEpisode.Path = Path.Combine(outputPath, "subfolder", name + ".mkv");
|
||||
|
||||
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, null);
|
||||
|
||||
@@ -9,7 +9,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenters.Quality
|
||||
namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators.Augmenters.Quality
|
||||
{
|
||||
[TestFixture]
|
||||
public class AugmentQualityFromReleaseNameFixture : CoreTest<AugmentQualityFromReleaseName>
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
|
||||
|
||||
_fileInfo = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitles = new List<string> { "The Office" },
|
||||
MovieTitle = "The Office",
|
||||
Year = 2018,
|
||||
Quality = _quality
|
||||
};
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.Movies.AlternativeTitles;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.MovieTests.MovieServiceTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class FindByTitleFixture : CoreTest<MovieService>
|
||||
{
|
||||
private List<Movie> _candidates;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_candidates = Builder<Movie>.CreateListOfSize(3)
|
||||
.TheFirst(1)
|
||||
.With(x => x.CleanTitle = "batman")
|
||||
.With(x => x.Year = 2000)
|
||||
.TheNext(1)
|
||||
.With(x => x.CleanTitle = "batman")
|
||||
.With(x => x.Year = 1999)
|
||||
.TheRest()
|
||||
.With(x => x.CleanTitle = "darkknight")
|
||||
.With(x => x.Year = 2008)
|
||||
.With(x => x.AlternativeTitles = new List<AlternativeTitle>
|
||||
{
|
||||
new AlternativeTitle
|
||||
{
|
||||
CleanTitle = "batman"
|
||||
}
|
||||
})
|
||||
.Build()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_find_by_title_year()
|
||||
{
|
||||
var movie = Subject.FindByTitle(new List<string> { "batman" }, 2000, new List<string>(), _candidates);
|
||||
|
||||
movie.Should().NotBeNull();
|
||||
movie.Year.Should().Be(2000);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_find_candidates_by_alt_titles()
|
||||
{
|
||||
var movie = Subject.FindByTitle(new List<string> { "batman" }, 2008, new List<string>(), _candidates);
|
||||
movie.Should().NotBeNull();
|
||||
movie.Year.Should().Be(2008);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
public void should_properly_parse_hashed_releases(string path, string title, Quality quality, string releaseGroup)
|
||||
{
|
||||
var result = Parser.Parser.ParseMoviePath(path);
|
||||
result.PrimaryMovieTitle.Should().Be(title);
|
||||
result.MovieTitle.Should().Be(title);
|
||||
result.Quality.Quality.Should().Be(quality);
|
||||
result.ReleaseGroup.Should().Be(releaseGroup);
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("www.Torrenting.org - Movie.2008.720p.X264-DIMENSION", "Movie")]
|
||||
public void should_parse_movie_title(string postTitle, string title)
|
||||
{
|
||||
Parser.Parser.ParseMovieTitle(postTitle).PrimaryMovieTitle.Should().Be(title);
|
||||
Parser.Parser.ParseMovieTitle(postTitle).MovieTitle.Should().Be(title);
|
||||
}
|
||||
|
||||
[TestCase("Movie.Aufbruch.nach.Pandora.Extended.2009.German.DTS.720p.BluRay.x264-SoW", "Movie Aufbruch nach Pandora", "Extended", 2009)]
|
||||
@@ -99,85 +99,16 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
ParsedMovieInfo movie = Parser.Parser.ParseMovieTitle(postTitle);
|
||||
using (new AssertionScope())
|
||||
{
|
||||
movie.PrimaryMovieTitle.Should().Be(title);
|
||||
movie.MovieTitle.Should().Be(title);
|
||||
movie.Edition.Should().Be(edition);
|
||||
movie.Year.Should().Be(year);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase("L'hypothèse.du.tableau.volé.AKA.The.Hypothesis.of.the.Stolen.Painting.1978.1080p.CINET.WEB-DL.AAC2.0.x264-Cinefeel.mkv",
|
||||
new string[]
|
||||
{
|
||||
"L'hypothèse du tableau volé AKA The Hypothesis of the Stolen Painting",
|
||||
"L'hypothèse du tableau volé",
|
||||
"The Hypothesis of the Stolen Painting"
|
||||
})]
|
||||
[TestCase("Akahige.AKA.Red.Beard.1965.CD1.CRiTERiON.DVDRip.XviD-KG.avi",
|
||||
new string[]
|
||||
{
|
||||
"Akahige AKA Red Beard",
|
||||
"Akahige",
|
||||
"Red Beard"
|
||||
})]
|
||||
[TestCase("Akasen.chitai.AKA.Street.of.Shame.1956.1080p.BluRay.x264.FLAC.1.0.mkv",
|
||||
new string[]
|
||||
{
|
||||
"Akasen chitai AKA Street of Shame",
|
||||
"Akasen chitai",
|
||||
"Street of Shame"
|
||||
})]
|
||||
[TestCase("Time.Under.Fire.(aka.Beneath.the.Bermuda.Triangle).1997.DVDRip.x264.CG-Grzechsin.mkv",
|
||||
new string[]
|
||||
{
|
||||
"Time Under Fire (aka Beneath the Bermuda Triangle)",
|
||||
"Time Under Fire",
|
||||
"Beneath the Bermuda Triangle"
|
||||
})]
|
||||
[TestCase("Nochnoy.prodavet. AKA.Graveyard.Shift.2005.DVDRip.x264-HANDJOB.mkv",
|
||||
new string[]
|
||||
{
|
||||
"Nochnoy prodavet AKA Graveyard Shift",
|
||||
"Nochnoy prodavet",
|
||||
"Graveyard Shift"
|
||||
})]
|
||||
[TestCase("AKA.2002.DVDRip.x264-HANDJOB.mkv",
|
||||
new string[]
|
||||
{
|
||||
"AKA"
|
||||
})]
|
||||
[TestCase("Unbreakable.2000.BluRay.1080p.DTS.x264.dxva-EuReKA.mkv",
|
||||
new string[]
|
||||
{
|
||||
"Unbreakable"
|
||||
})]
|
||||
[TestCase("Aka Ana (2008).avi",
|
||||
new string[]
|
||||
{
|
||||
"Aka Ana"
|
||||
})]
|
||||
[TestCase("Return to Return to Nuke 'em High aka Volume 2 (2018) 1080p.mp4",
|
||||
new string[]
|
||||
{
|
||||
"Return to Return to Nuke 'em High aka Volume 2",
|
||||
"Return to Return to Nuke 'em High",
|
||||
"Volume 2"
|
||||
})]
|
||||
public void should_parse_movie_alternative_titles(string postTitle, string[] parsedTitles)
|
||||
{
|
||||
var movieInfo = Parser.Parser.ParseMovieTitle(postTitle, true);
|
||||
|
||||
movieInfo.MovieTitles.Count.Should().Be(parsedTitles.Length);
|
||||
|
||||
for (var i = 0; i < movieInfo.MovieTitles.Count; i += 1)
|
||||
{
|
||||
movieInfo.MovieTitles[i].Should().Be(parsedTitles[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase("(1995) Movie Name", "Movie Name")]
|
||||
public void should_parse_movie_folder_name(string postTitle, string title)
|
||||
{
|
||||
Parser.Parser.ParseMovieTitle(postTitle, true).PrimaryMovieTitle.Should().Be(title);
|
||||
Parser.Parser.ParseMovieTitle(postTitle, true).MovieTitle.Should().Be(title);
|
||||
}
|
||||
|
||||
[TestCase("1776.1979.EXTENDED.720p.BluRay.X264-AMIABLE", 1979)]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Parser.Augmenters;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
@@ -18,7 +17,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests.AugmentersTests
|
||||
{
|
||||
MovieInfo = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitles = new List<string> { "A Movie" },
|
||||
MovieTitle = "A Movie",
|
||||
Year = 1998,
|
||||
SimpleReleaseTitle = "A Movie Title 1998 Bluray 1080p",
|
||||
Quality = new QualityModel(Quality.Bluray1080p)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Collections.Generic;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Movies;
|
||||
@@ -29,7 +28,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||
Subject.GetMovie(title);
|
||||
|
||||
Mocker.GetMock<IMovieService>()
|
||||
.Verify(s => s.FindByTitle(Parser.Parser.ParseMovieTitle(title, false).MovieTitles, It.IsAny<int>(), It.IsAny<List<string>>(), null), Times.Once());
|
||||
.Verify(s => s.FindByTitle(Parser.Parser.ParseMovieTitle(title, false).MovieTitle, It.IsAny<int>(), null, null, null), Times.Once());
|
||||
}
|
||||
|
||||
/*[Test]
|
||||
|
||||
@@ -45,69 +45,69 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||
|
||||
_parsedMovieInfo = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitles = new List<string> { _movie.Title },
|
||||
MovieTitle = _movie.Title,
|
||||
Languages = new List<Language> { Language.English },
|
||||
Year = _movie.Year,
|
||||
};
|
||||
|
||||
_wrongYearInfo = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitles = new List<string> { _movie.Title },
|
||||
MovieTitle = _movie.Title,
|
||||
Languages = new List<Language> { Language.English },
|
||||
Year = 1900,
|
||||
};
|
||||
|
||||
_wrongTitleInfo = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitles = new List<string> { "Other Title" },
|
||||
MovieTitle = "Other Title",
|
||||
Languages = new List<Language> { Language.English },
|
||||
Year = 2015
|
||||
};
|
||||
|
||||
_alternativeTitleInfo = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitles = new List<string> { _movie.AlternativeTitles.First().Title },
|
||||
MovieTitle = _movie.AlternativeTitles.First().Title,
|
||||
Languages = new List<Language> { Language.English },
|
||||
Year = _movie.Year,
|
||||
};
|
||||
|
||||
_translationTitleInfo = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitles = new List<string> { _movie.Translations.First().Title },
|
||||
MovieTitle = _movie.Translations.First().Title,
|
||||
Languages = new List<Language> { Language.English },
|
||||
Year = _movie.Year,
|
||||
};
|
||||
|
||||
_romanTitleInfo = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitles = new List<string> { "Fack Ju Göthe II" },
|
||||
MovieTitle = "Fack Ju Göthe II",
|
||||
Languages = new List<Language> { Language.English },
|
||||
Year = _movie.Year,
|
||||
};
|
||||
|
||||
_umlautInfo = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitles = new List<string> { "Fack Ju Goethe 2" },
|
||||
MovieTitle = "Fack Ju Goethe 2",
|
||||
Languages = new List<Language> { Language.English },
|
||||
Year = _movie.Year
|
||||
};
|
||||
|
||||
_umlautAltInfo = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitles = new List<string> { "Fack Ju Goethe 2: Same same" },
|
||||
MovieTitle = "Fack Ju Goethe 2: Same same",
|
||||
Languages = new List<Language> { Language.English },
|
||||
Year = _movie.Year
|
||||
};
|
||||
|
||||
_multiLanguageInfo = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitles = { _movie.Title },
|
||||
MovieTitle = _movie.Title,
|
||||
Languages = new List<Language> { Language.Original, Language.French }
|
||||
};
|
||||
|
||||
_multiLanguageWithOriginalInfo = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitles = { _movie.Title },
|
||||
MovieTitle = _movie.Title,
|
||||
Languages = new List<Language> { Language.Original, Language.French, Language.English }
|
||||
};
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||
Subject.Map(_parsedMovieInfo, "", null);
|
||||
|
||||
Mocker.GetMock<IMovieService>()
|
||||
.Verify(v => v.FindByTitle(It.IsAny<List<string>>(), It.IsAny<int>(), It.IsAny<List<string>>(), null), Times.Once());
|
||||
.Verify(v => v.FindByTitle(It.IsAny<string>(), It.IsAny<int>(), null, null, null), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -87,8 +87,6 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Movie.Name.S06E11.The.Santa.Simulation.480p.WEB-DL.x264-mSD", false)]
|
||||
[TestCase("Movie.Name.S02E04.480p.WEB.DL.nSD.x264-NhaNc3", false)]
|
||||
[TestCase("[HorribleSubs] Movie Title! 2018 [Web][MKV][h264][480p][AAC 2.0][Softsubs (HorribleSubs)]", false)]
|
||||
[TestCase("[SubsPlease] Movie Title (540p) [AB649D32].mkv", false)]
|
||||
[TestCase("[Erai-raws] Movie Title [540p][Multiple Subtitle].mkv", false)]
|
||||
public void should_parse_webdl480p_quality(string title, bool proper)
|
||||
{
|
||||
ParseAndVerifyQuality(title, Source.WEBDL, proper, Resolution.R480p);
|
||||
@@ -150,7 +148,6 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Movie Name.S01E01.The.Insanity.Principle.720p.WEB-DL.DD5.1.H.264-BD", false)]
|
||||
[TestCase("[HorribleSubs] Movie Title! 2018 [Web][MKV][h264][720p][AAC 2.0][Softsubs (HorribleSubs)]", false)]
|
||||
[TestCase("[HorribleSubs] Movie Title! 2018 [Web][MKV][h264][AAC 2.0][Softsubs (HorribleSubs)]", false)]
|
||||
[TestCase("Movie.Title.2013.960p.WEB-DL.AAC2.0.H.264-squalor", false)]
|
||||
public void should_parse_webdl720p_quality(string title, bool proper)
|
||||
{
|
||||
ParseAndVerifyQuality(title, Source.WEBDL, proper, Resolution.R720p);
|
||||
@@ -289,7 +286,6 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Movie.Name.2008.BDREMUX.1080p.Bluray.AVC.DTS-HR.MA.5.1-LEGi0N")]
|
||||
[TestCase("Movie.Title.M.2008.USA.BluRay.Remux.1080p.MPEG-2.DD.5.1-TDD")]
|
||||
[TestCase("Movie.Title.2018.1080p.BluRay.REMUX.MPEG-2.DTS-HD.MA.5.1-EPSiLON")]
|
||||
[TestCase("Movie.Title.II.2003.4K.BluRay.Remux.1080p.AVC.DTS-HD.MA.5.1-BMF")]
|
||||
public void should_parse_remux1080p_quality(string title)
|
||||
{
|
||||
ParseAndVerifyQuality(title, Source.BLURAY, false, Resolution.R1080p, Modifier.REMUX);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net5.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.0.123" />
|
||||
<PackageReference Include="Dapper" Version="2.0.90" />
|
||||
<PackageReference Include="NBuilder" Version="6.1.0" />
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.113.0-0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Test.Common\Radarr.Test.Common.csproj" />
|
||||
|
||||
@@ -23,7 +23,6 @@ namespace NzbDrone.Core.Annotations
|
||||
public string Section { get; set; }
|
||||
public HiddenType Hidden { get; set; }
|
||||
public PrivacyLevel Privacy { get; set; }
|
||||
public string Placeholder { get; set; }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
|
||||
|
||||
@@ -85,8 +85,7 @@ namespace NzbDrone.Core.Blocklisting
|
||||
Size = remoteMovie.Release.Size,
|
||||
Indexer = remoteMovie.Release.Indexer,
|
||||
Protocol = remoteMovie.Release.DownloadProtocol,
|
||||
Message = message,
|
||||
Languages = remoteMovie.ParsedMovieInfo.Languages
|
||||
Message = message
|
||||
};
|
||||
|
||||
if (remoteMovie.Release is TorrentInfo torrentRelease)
|
||||
|
||||
@@ -43,6 +43,7 @@ namespace NzbDrone.Core.Configuration
|
||||
string SslCertPassword { get; }
|
||||
string UrlBase { get; }
|
||||
string UiFolder { get; }
|
||||
string Theme { get; }
|
||||
bool UpdateAutomatically { get; }
|
||||
UpdateMechanism UpdateMechanism { get; }
|
||||
string UpdateScriptPath { get; }
|
||||
@@ -140,6 +141,8 @@ namespace NzbDrone.Core.Configuration
|
||||
|
||||
public int SslPort => GetValueInt("SslPort", 9898);
|
||||
|
||||
public string Theme => GetValue("Theme", "default");
|
||||
|
||||
public bool EnableSsl => GetValueBoolean("EnableSsl", false);
|
||||
|
||||
public bool LaunchBrowser => GetValueBoolean("LaunchBrowser", true);
|
||||
|
||||
@@ -192,6 +192,13 @@ namespace NzbDrone.Core.Configuration
|
||||
set { SetValue("WhitelistedHardcodedSubs", value); }
|
||||
}
|
||||
|
||||
public bool RemoveCompletedDownloads
|
||||
{
|
||||
get { return GetValueBoolean("RemoveCompletedDownloads", false); }
|
||||
|
||||
set { SetValue("RemoveCompletedDownloads", value); }
|
||||
}
|
||||
|
||||
public bool AutoRedownloadFailed
|
||||
{
|
||||
get { return GetValueBoolean("AutoRedownloadFailed", true); }
|
||||
@@ -199,6 +206,13 @@ namespace NzbDrone.Core.Configuration
|
||||
set { SetValue("AutoRedownloadFailed", value); }
|
||||
}
|
||||
|
||||
public bool RemoveFailedDownloads
|
||||
{
|
||||
get { return GetValueBoolean("RemoveFailedDownloads", true); }
|
||||
|
||||
set { SetValue("RemoveFailedDownloads", value); }
|
||||
}
|
||||
|
||||
public bool CreateEmptyMovieFolders
|
||||
{
|
||||
get { return GetValueBoolean("CreateEmptyMovieFolders", false); }
|
||||
|
||||
@@ -21,8 +21,10 @@ namespace NzbDrone.Core.Configuration
|
||||
|
||||
//Completed/Failed Download Handling (Download client)
|
||||
bool EnableCompletedDownloadHandling { get; set; }
|
||||
bool RemoveCompletedDownloads { get; set; }
|
||||
|
||||
bool AutoRedownloadFailed { get; set; }
|
||||
bool RemoveFailedDownloads { get; set; }
|
||||
|
||||
//Media Management
|
||||
bool AutoUnmonitorPreviouslyDownloadedMovies { get; set; }
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace NzbDrone.Core.CustomFormats
|
||||
|
||||
var info = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitles = new List<string>() { movieFile.Movie.Title },
|
||||
MovieTitle = movieFile.Movie.Title,
|
||||
SimpleReleaseTitle = sceneName.SimplifyReleaseTitle(),
|
||||
Quality = movieFile.Quality,
|
||||
Languages = movieFile.Languages,
|
||||
@@ -112,7 +112,7 @@ namespace NzbDrone.Core.CustomFormats
|
||||
|
||||
var info = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitles = new List<string>() { movie.Title },
|
||||
MovieTitle = movie.Title,
|
||||
SimpleReleaseTitle = parsed?.SimpleReleaseTitle ?? blocklist.SourceTitle.SimplifyReleaseTitle(),
|
||||
Quality = blocklist.Quality,
|
||||
Languages = blocklist.Languages,
|
||||
@@ -140,7 +140,7 @@ namespace NzbDrone.Core.CustomFormats
|
||||
|
||||
var info = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitles = new List<string>() { movie.Title },
|
||||
MovieTitle = movie.Title,
|
||||
SimpleReleaseTitle = parsed?.SimpleReleaseTitle ?? history.SourceTitle.SimplifyReleaseTitle(),
|
||||
Quality = history.Quality,
|
||||
Languages = history.Languages,
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using FluentMigrator;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(200)]
|
||||
public class cdh_per_downloadclient : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("DownloadClients")
|
||||
.AddColumn("RemoveCompletedDownloads").AsBoolean().NotNullable().WithDefaultValue(true)
|
||||
.AddColumn("RemoveFailedDownloads").AsBoolean().NotNullable().WithDefaultValue(true);
|
||||
|
||||
Execute.WithConnection(MoveRemoveSettings);
|
||||
}
|
||||
|
||||
private void MoveRemoveSettings(IDbConnection conn, IDbTransaction tran)
|
||||
{
|
||||
var removeCompletedDownloads = false;
|
||||
var removeFailedDownloads = true;
|
||||
|
||||
using (var removeCompletedDownloadsCmd = conn.CreateCommand(tran, "SELECT Value FROM Config WHERE Key = 'removecompleteddownloads'"))
|
||||
{
|
||||
if ((removeCompletedDownloadsCmd.ExecuteScalar() as string)?.ToLower() == "true")
|
||||
{
|
||||
removeCompletedDownloads = true;
|
||||
}
|
||||
}
|
||||
|
||||
using (var removeFailedDownloadsCmd = conn.CreateCommand(tran, "SELECT Value FROM Config WHERE Key = 'removefaileddownloads'"))
|
||||
{
|
||||
if ((removeFailedDownloadsCmd.ExecuteScalar() as string)?.ToLower() == "false")
|
||||
{
|
||||
removeFailedDownloads = false;
|
||||
}
|
||||
}
|
||||
|
||||
using (var updateClientCmd = conn.CreateCommand(tran, $"UPDATE DownloadClients SET RemoveCompletedDownloads = (CASE WHEN Implementation IN (\"RTorrent\", \"Flood\") THEN 0 ELSE ? END), RemoveFailedDownloads = ?"))
|
||||
{
|
||||
updateClientCmd.AddParameter(removeCompletedDownloads ? 1 : 0);
|
||||
updateClientCmd.AddParameter(removeFailedDownloads ? 1 : 0);
|
||||
updateClientCmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
using (var removeConfigCmd = conn.CreateCommand(tran, $"DELETE FROM Config WHERE Key IN ('removecompleteddownloads', 'removefaileddownloads')"))
|
||||
{
|
||||
removeConfigCmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Data;
|
||||
using FluentMigrator;
|
||||
using FluentMigrator;
|
||||
using FluentMigrator.Builders.Create;
|
||||
using FluentMigrator.Builders.Create.Table;
|
||||
using FluentMigrator.Runner;
|
||||
@@ -17,15 +16,6 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
return expressionRoot.Table(name).WithColumn("Id").AsInt32().PrimaryKey().Identity();
|
||||
}
|
||||
|
||||
public static IDbCommand CreateCommand(this IDbConnection conn, IDbTransaction tran, string query)
|
||||
{
|
||||
var command = conn.CreateCommand();
|
||||
command.Transaction = tran;
|
||||
command.CommandText = query;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
public static void AddParameter(this System.Data.IDbCommand command, object value)
|
||||
{
|
||||
var parameter = command.CreateParameter();
|
||||
|
||||
@@ -77,12 +77,12 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
|
||||
MappingResult result = null;
|
||||
|
||||
if (parsedMovieInfo == null || parsedMovieInfo.PrimaryMovieTitle.IsNullOrWhiteSpace())
|
||||
if (parsedMovieInfo == null || parsedMovieInfo.MovieTitle.IsNullOrWhiteSpace())
|
||||
{
|
||||
_logger.Debug("{0} could not be parsed :(.", report.Title);
|
||||
parsedMovieInfo = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitles = new List<string>() { report.Title },
|
||||
MovieTitle = report.Title,
|
||||
SimpleReleaseTitle = report.Title.SimplifyReleaseTitle(),
|
||||
Year = 1290,
|
||||
Languages = new List<Language> { Language.Unknown },
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -11,12 +10,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
public class IndexerTagSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
private readonly IIndexerRepository _indexerRepository;
|
||||
|
||||
public IndexerTagSpecification(Logger logger, IIndexerFactory indexerFactory)
|
||||
public IndexerTagSpecification(Logger logger, IIndexerRepository indexerRepository)
|
||||
{
|
||||
_logger = logger;
|
||||
_indexerFactory = indexerFactory;
|
||||
_indexerRepository = indexerRepository;
|
||||
}
|
||||
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
@@ -24,24 +23,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (subject.Release == null || subject.Movie?.Tags == null || subject.Release.IndexerId == 0)
|
||||
{
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
IndexerDefinition indexer;
|
||||
try
|
||||
{
|
||||
indexer = _indexerFactory.Get(subject.Release.IndexerId);
|
||||
}
|
||||
catch (ModelNotFoundException)
|
||||
{
|
||||
_logger.Debug("Indexer with id {0} does not exist, skipping indexer tags check", subject.Release.IndexerId);
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
// If indexer has tags, check that at least one of them is present on the series
|
||||
var indexerTags = indexer.Tags;
|
||||
var indexerTags = _indexerRepository.Get(subject.Release.IndexerId).Tags;
|
||||
|
||||
if (indexerTags.Any() && indexerTags.Intersect(subject.Movie.Tags).Empty())
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Http;
|
||||
@@ -143,19 +142,15 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
|
||||
return authResponse.Data.SId;
|
||||
}
|
||||
|
||||
protected HttpRequestBuilder BuildRequest(DownloadStationSettings settings, string methodName, int apiVersion, HttpMethod httpVerb = null)
|
||||
protected HttpRequestBuilder BuildRequest(DownloadStationSettings settings, string methodName, int apiVersion, HttpMethod httpVerb = HttpMethod.GET)
|
||||
{
|
||||
httpVerb ??= HttpMethod.Get;
|
||||
|
||||
var info = GetApiInfo(_apiType, settings);
|
||||
|
||||
return BuildRequest(settings, info, methodName, apiVersion, httpVerb);
|
||||
}
|
||||
|
||||
private HttpRequestBuilder BuildRequest(DownloadStationSettings settings, DiskStationApiInfo apiInfo, string methodName, int apiVersion, HttpMethod httpVerb = null)
|
||||
private HttpRequestBuilder BuildRequest(DownloadStationSettings settings, DiskStationApiInfo apiInfo, string methodName, int apiVersion, HttpMethod httpVerb = HttpMethod.GET)
|
||||
{
|
||||
httpVerb ??= HttpMethod.Get;
|
||||
|
||||
var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port).Resource($"webapi/{apiInfo.Path}");
|
||||
requestBuilder.Method = httpVerb;
|
||||
requestBuilder.LogResponseContent = true;
|
||||
@@ -168,7 +163,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
|
||||
throw new ArgumentOutOfRangeException(nameof(apiVersion));
|
||||
}
|
||||
|
||||
if (httpVerb == HttpMethod.Post)
|
||||
if (httpVerb == HttpMethod.POST)
|
||||
{
|
||||
if (apiInfo.NeedsAuthentication)
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user