1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-18 16:24:34 -04:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Mark McDowall
da5feda71b Fixed: Parsing of release names with trailing colon in the title 2021-10-30 22:55:42 -04:00
271 changed files with 3503 additions and 4546 deletions

View File

@@ -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

View File

@@ -1,4 +1,5 @@
name: Feature Request
title: "[FEAT]: "
description: 'Suggest an idea for Radarr'
labels: ['Type: Feature Request', 'Status: Needs Triage']
body:

View File

@@ -1,21 +1,19 @@
# Radarr
[![Build Status](https://dev.azure.com/Radarr/Radarr/_apis/build/status/Radarr.Radarr?branchName=develop)](https://dev.azure.com/Radarr/Radarr/_build/latest?definitionId=1&branchName=develop)
[![Translated](https://translate.servarr.com/widgets/servarr/-/radarr/svg-badge.svg)](https://translate.servarr.com/engage/radarr/?utm_source=widget)
[![Translated](https://translate.servarr.com/widgets/radarr/-/radarr/svg-badge.svg)](https://translate.servarr.com/engage/radarr/?utm_source=widget)
[![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/radarr.svg)](https://wiki.servarr.com/radarr/installation#docker)
![Github Downloads](https://img.shields.io/github/downloads/Radarr/Radarr/total.svg)
[![Backers on Open Collective](https://opencollective.com/Radarr/backers/badge.svg)](#backers)
[![Backers on Open Collective](https://opencollective.com/Radarr/backers/badge.svg)](#backers)
[![Sponsors on Open Collective](https://opencollective.com/Radarr/sponsors/badge.svg)](#sponsors)
[![Mega Sponsors on Open Collective](https://opencollective.com/Radarr/megasponsors/badge.svg)](#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
[![Wiki](https://img.shields.io/badge/servarr-wiki-181717.svg?maxAge=60)](https://wiki.servarr.com/radarr)
[![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60)](https://radarr.video/discord)
[![Reddit](https://img.shields.io/badge/reddit-discussion-FF4500.svg?maxAge=60)](https://www.reddit.com/r/Radarr)
Note: GitHub Issues are for Bugs and Feature Requests Only
[![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60)](https://radarr.video/discord)
[![Reddit](https://img.shields.io/badge/reddit-discussion-FF4500.svg?maxAge=60)](https://www.reddit.com/r/Radarr)
[![GitHub - Bugs and Feature Requests Only](https://img.shields.io/badge/github-issues-red.svg?maxAge=60)](https://github.com/Radarr/Radarr/issues)
[![Wiki](https://img.shields.io/badge/servarr-wiki-181717.svg?maxAge=60)](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>
[![Contributors List](https://opencollective.com/Radarr/contributors.svg?width=890&button=false)](https://github.com/Radarr/Radarr/graphs/contributors)
## Backers
Thank you to all our backers! 🙏 [Become a backer](https://opencollective.com/Radarr#backer)
[![Backers List](https://opencollective.com/Radarr/backers.svg?width=890)](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)
[![Sponsors List](https://opencollective.com/Radarr/sponsors.svg?width=890)](https://opencollective.com/Radarr#sponsor)
<img src="https://opencollective.com/Radarr/sponsors.svg?width=890"></a>
## Mega Sponsors
[![Mega Sponsors List](https://opencollective.com/Radarr/tiers/mega-sponsor.svg?width=890)](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

View File

@@ -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:
@@ -67,7 +67,7 @@ stages:
enableAnalysis: 'true'
Mac:
osName: 'Mac'
imageName: 'macos-10.15'
imageName: 'macos-10.14'
enableAnalysis: 'false'
Windows:
osName: 'Windows'
@@ -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'))
@@ -144,7 +144,7 @@ stages:
imageName: 'ubuntu-18.04'
Mac:
osName: 'Mac'
imageName: 'macos-10.15'
imageName: 'macos-10.14'
Windows:
osName: 'Windows'
imageName: 'windows-2019'
@@ -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'
@@ -241,7 +241,6 @@ stages:
- bash: ./build.sh --packages --enable-bsd
displayName: Create Packages
- bash: |
find . -name "ffprobe" -exec chmod a+x {} \;
find . -name "Radarr" -exec chmod a+x {} \;
find . -name "Radarr.Update" -exec chmod a+x {} \;
displayName: Set executable bits
@@ -251,44 +250,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 +280,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 +288,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 +296,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 +304,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 +312,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 +320,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
@@ -407,7 +383,7 @@ stages:
osName: 'Mac'
testName: 'MacCore'
poolName: 'Azure Pipelines'
imageName: 'macos-10.15'
imageName: 'macos-10.14'
WindowsCore:
osName: 'Windows'
testName: 'WindowsCore'
@@ -441,13 +417,16 @@ stages:
buildType: 'current'
artifactName: '$(testName)Tests'
targetPath: $(testsFolder)
- bash: |
wget https://mediaarea.net/repo/deb/repo-mediaarea_1.0-11_all.deb
sudo dpkg -i repo-mediaarea_1.0-11_all.deb
sudo apt-get update
sudo apt-get install -y --allow-unauthenticated libmediainfo-dev libmediainfo0v5 mediainfo
displayName: Install mediainfo
condition: and(succeeded(), eq(variables['testName'], 'LinuxCore'))
- powershell: Set-Service SCardSvr -StartupType Manual
displayName: Enable Windows Test Service
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- bash: |
chmod a+x _tests/ffprobe
displayName: Make ffprobe Executable
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
- bash: find ${TESTSFOLDER} -name "Radarr.Test.Dummy" -exec chmod a+x {} \;
displayName: Make Test Dummy Executable
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
@@ -495,9 +474,6 @@ stages:
buildType: 'current'
artifactName: $(artifactName)
targetPath: $(testsFolder)
- bash: |
chmod a+x _tests/ffprobe
displayName: Make ffprobe Executable
- bash: find ${TESTSFOLDER} -name "Radarr.Test.Dummy" -exec chmod a+x {} \;
displayName: Make Test Dummy Executable
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
@@ -541,7 +517,7 @@ stages:
MacCore:
osName: 'Mac'
testName: 'MacCore'
imageName: 'macos-10.15'
imageName: 'macos-10.14'
pattern: 'Radarr.*.osx-core-x64.tar.gz'
WindowsCore:
osName: 'Windows'
@@ -716,7 +692,7 @@ stages:
failBuild: true
Mac:
osName: 'Mac'
imageName: 'macos-10.15'
imageName: 'macos-10.14'
pattern: 'Radarr.*.osx-core-x64.tar.gz'
failBuild: true
Windows:
@@ -889,8 +865,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')

View File

@@ -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"

View File

@@ -282,17 +282,6 @@ class Queue extends Component {
return !!(item && item.movieId);
})
)}
allPending={isConfirmRemoveModalOpen && (
selectedIds.every((id) => {
const item = items.find((i) => i.id === id);
if (!item) {
return false;
}
return item.status === 'delay' || item.status === 'downloadClientUnavailable';
})
)}
onRemovePress={this.onRemoveSelectedConfirmed}
onModalClose={this.onConfirmRemoveModalClose}
/>

View File

@@ -332,7 +332,6 @@ class QueueRow extends Component {
isOpen={isRemoveQueueItemModalOpen}
sourceTitle={title}
canIgnore={!!movie}
isPending={isPending}
onRemovePress={this.onRemoveQueueItemModalConfirmed}
onModalClose={this.onRemoveQueueItemModalClose}
/>

View File

@@ -66,8 +66,7 @@ class RemoveQueueItemModal extends Component {
const {
isOpen,
sourceTitle,
canIgnore,
isPending
canIgnore
} = this.props;
const { remove, blocklist } = this.state;
@@ -90,22 +89,18 @@ class RemoveQueueItemModal extends Component {
{translate('RemoveFromQueueText', [sourceTitle])}
</div>
{
isPending ?
null :
<FormGroup>
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
<FormGroup>
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="remove"
value={remove}
helpTextWarning={translate('RemoveHelpTextWarning')}
isDisabled={!canIgnore}
onChange={this.onRemoveChange}
/>
</FormGroup>
}
<FormInputGroup
type={inputTypes.CHECK}
name="remove"
value={remove}
helpTextWarning={translate('RemoveHelpTextWarning')}
isDisabled={!canIgnore}
onChange={this.onRemoveChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('BlocklistRelease')}</FormLabel>
@@ -142,7 +137,6 @@ RemoveQueueItemModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
sourceTitle: PropTypes.string.isRequired,
canIgnore: PropTypes.bool.isRequired,
isPending: PropTypes.bool.isRequired,
onRemovePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -67,8 +67,7 @@ class RemoveQueueItemsModal extends Component {
const {
isOpen,
selectedCount,
canIgnore,
allPending
canIgnore
} = this.props;
const { remove, blocklist } = this.state;
@@ -83,34 +82,30 @@ class RemoveQueueItemsModal extends Component {
onModalClose={this.onModalClose}
>
<ModalHeader>
{selectedCount > 1 ? translate('RemoveSelectedItems') : translate('RemoveSelectedItem')}
Remove Selected Item{selectedCount > 1 ? 's' : ''}
</ModalHeader>
<ModalBody>
<div className={styles.message}>
{selectedCount > 1 ? translate('AreYouSureYouWantToRemoveSelectedItemsFromQueue', selectedCount) : translate('AreYouSureYouWantToRemoveSelectedItemFromQueue')}
{translate('AreYouSureYouWantToRemoveSelectedItemsFromQueue', [selectedCount, selectedCount > 1 ? 's' : ''])}
</div>
{
allPending ?
null :
<FormGroup>
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
<FormGroup>
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="remove"
value={remove}
helpTextWarning={translate('RemoveHelpTextWarning')}
isDisabled={!canIgnore}
onChange={this.onRemoveChange}
/>
</FormGroup>
}
<FormInputGroup
type={inputTypes.CHECK}
name="remove"
value={remove}
helpTextWarning={translate('RemoveHelpTextWarning')}
isDisabled={!canIgnore}
onChange={this.onRemoveChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{selectedCount > 1 ? translate('BlocklistReleases') : translate('BlocklistRelease')}
Blocklist Release{selectedCount > 1 ? 's' : ''}
</FormLabel>
<FormInputGroup
@@ -146,7 +141,6 @@ RemoveQueueItemsModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
selectedCount: PropTypes.number.isRequired,
canIgnore: PropTypes.bool.isRequired,
allPending: PropTypes.bool.isRequired,
onRemovePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -16,9 +16,4 @@
color: #3a3f51;
font-size: 21px;
line-height: inherit;
&.small {
color: #909293;
font-size: 18px;
}
}

View File

@@ -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;

View File

@@ -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,

View File

@@ -151,8 +151,7 @@
.qualityProfileName,
.statusName,
.studio,
.collection,
.genres {
.collection {
font-weight: 300;
font-size: 17px;
}

View File

@@ -266,7 +266,6 @@ class MovieDetails extends Component {
qualityProfileId,
monitored,
studio,
genres,
collection,
overview,
youTubeTrailerId,
@@ -583,19 +582,6 @@ class MovieDetails extends Component {
</span>
</InfoLabel>
}
{
!!genres.length && !isSmallScreen &&
<InfoLabel
className={styles.detailsInfoLabel}
title={translate('Genres')}
size={sizes.LARGE}
>
<span className={styles.genres}>
{genres.join(', ')}
</span>
</InfoLabel>
}
</div>
<Measure onMeasure={this.onMeasure}>
@@ -780,7 +766,6 @@ MovieDetails.propTypes = {
monitored: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired,
studio: PropTypes.string,
genres: PropTypes.arrayOf(PropTypes.string).isRequired,
collection: PropTypes.object,
youTubeTrailerId: PropTypes.string,
isAvailable: PropTypes.bool.isRequired,
@@ -813,7 +798,6 @@ MovieDetails.propTypes = {
};
MovieDetails.defaultProps = {
genres: [],
tags: [],
isSaving: false,
sizeOnDisk: 0

View File

@@ -33,9 +33,3 @@
width: 100px;
}
.releaseGroup {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 120px;
}

View File

@@ -74,7 +74,6 @@ class MovieFileEditorRow extends Component {
mediaInfo,
relativePath,
size,
releaseGroup,
quality,
qualityCutoffNotMet,
customFormats,
@@ -156,12 +155,6 @@ class MovieFileEditorRow extends Component {
}
</TableRowCell>
<TableRowCell
className={styles.releaseGroup}
>
{releaseGroup}
</TableRowCell>
<TableRowCell
className={styles.formats}
>
@@ -223,7 +216,6 @@ MovieFileEditorRow.propTypes = {
size: PropTypes.number.isRequired,
relativePath: PropTypes.string.isRequired,
quality: PropTypes.object.isRequired,
releaseGroup: PropTypes.string,
customFormats: PropTypes.arrayOf(PropTypes.object).isRequired,
qualityCutoffNotMet: PropTypes.bool.isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired,

View File

@@ -39,11 +39,6 @@ const columns = [
label: translate('Quality'),
isVisible: true
},
{
name: 'releaseGroup',
label: translate('ReleaseGroup'),
isVisible: true
},
{
name: 'quality.customFormats',
label: translate('Formats'),

View File

@@ -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>

View File

@@ -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>
}

View File

@@ -14,7 +14,6 @@ function createRemoveItemHandler(section, url) {
const ajaxOptions = {
url: `${url}/${id}?${$.param(queryParams, true)}`,
dataType: 'text',
method: 'DELETE'
};

View File

@@ -600,7 +600,6 @@ export const actionHandlers = handleThunks({
const promise = createAjaxRequest({
url: '/exclusions/bulk',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(exclusions)
}).request;

View File

@@ -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"
}
}

View File

@@ -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>

View File

@@ -6,7 +6,7 @@
<add key="dotnet-bsd-crossbuild" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/dotnet-bsd-crossbuild/nuget/v3/index.json" />
<add key="Mono.Posix.NETStandard" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/Mono.Posix.NETStandard/nuget/v3/index.json" />
<add key="SQLite" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/SQLite/nuget/v3/index.json" />
<add key="FluentMigrator" value="https://pkgs.dev.azure.com/fluentmigrator/fluentmigrator/_packaging/fluentmigrator/nuget/v3/index.json" />
<add key="coverlet-nightly" value="https://pkgs.dev.azure.com/Servarr/coverlet/_packaging/coverlet-nightly/nuget/v3/index.json" />
<add key="FFMpegCore" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/FFMpegCore/nuget/v3/index.json" />
</packageSources>
</configuration>

View File

@@ -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" />

View File

@@ -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" />

View File

@@ -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

View File

@@ -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""}")]

View File

@@ -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" />

View File

@@ -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);

View File

@@ -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();

View File

@@ -75,6 +75,10 @@ namespace NzbDrone.Common.Composition
{
mappedName = "libsqlite3.so.0";
}
else if (libraryName == "mediainfo")
{
mappedName = "libmediainfo.so.0";
}
}
return NativeLibrary.Load(mappedName, assembly, dllImportSearchPath);

View File

@@ -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;

View File

@@ -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,

View File

@@ -287,11 +287,6 @@ namespace NzbDrone.Common.Extensions
return appFolderInfo.AppDataFolder;
}
public static string GetDataProtectionPath(this IAppFolderInfo appFolderInfo)
{
return Path.Combine(GetAppDataPath(appFolderInfo), "asp");
}
public static string GetLogFolder(this IAppFolderInfo appFolderInfo)
{
return Path.Combine(GetAppDataPath(appFolderInfo), "logs");

View File

@@ -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);
}
}

View File

@@ -5,5 +5,6 @@ namespace NzbDrone.Common.Http.Dispatchers
public interface IHttpDispatcher
{
HttpResponse GetResponse(HttpRequest request, CookieContainer cookies);
void DownloadFile(string url, string fileName);
}
}

View File

@@ -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;
}
}
}
}

View 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;
}
}
}

View File

@@ -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);
}

View File

@@ -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()
{
}

View File

@@ -0,0 +1,14 @@
namespace NzbDrone.Common.Http
{
public enum HttpMethod
{
GET,
POST,
PUT,
DELETE,
HEAD,
OPTIONS,
PATCH,
MERGE
}
}

View File

@@ -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()
{

View File

@@ -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.");
}

View File

@@ -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();
}

View File

@@ -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;
}
}
}

View File

@@ -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),

View File

@@ -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>

View File

@@ -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>

View 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>

View 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>

View File

@@ -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; }
}
}

View File

@@ -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;

View File

@@ -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();
}
}
}

View File

@@ -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,

View File

@@ -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
});
}

View File

@@ -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
}
};

View File

@@ -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());
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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());
}
}
}

View File

@@ -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);

View File

@@ -0,0 +1,321 @@
using System.Globalization;
using System.Threading;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
{
[TestFixture]
public class FormatAudioChannelsFixture : TestBase
{
[Test]
public void should_subtract_one_from_AudioChannels_as_total_channels_if_LFE_in_AudioChannelPositionsText()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannelsContainer = 6,
AudioChannelPositions = null,
AudioChannelPositionsTextContainer = "Front: L C R, Side: L R, LFE"
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
}
[Test]
public void should_use_AudioChannels_as_total_channels_if_LFE_not_in_AudioChannelPositionsText()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannelsContainer = 2,
AudioChannelPositions = null,
AudioChannelPositionsTextContainer = "Front: L R"
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
}
[Test]
public void should_return_0_if_schema_revision_is_less_than_3_and_other_properties_are_null()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannelsContainer = 2,
AudioChannelPositions = null,
AudioChannelPositionsTextContainer = null,
SchemaRevision = 2
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(0);
}
[Test]
public void should_use_AudioChannels_if_schema_revision_is_3_and_other_properties_are_null()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannelsContainer = 2,
AudioChannelPositions = null,
AudioChannelPositionsTextContainer = null,
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
}
[Test]
public void should_use_AudioChannels_if_schema_revision_is_3_and_AudioChannelPositions_is_0()
{
var mediaInfoModel = new MediaInfoModel
{
AudioFormat = "FLAC",
AudioChannelsContainer = 6,
AudioChannelPositions = "0/0/0",
AudioChannelPositionsTextContainer = null,
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
}
[Test]
public void should_sum_AudioChannelPositions()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannelsContainer = 2,
AudioChannelPositions = "2/0/0",
AudioChannelPositionsTextContainer = null,
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
}
[Test]
public void should_sum_AudioChannelPositions_including_decimal()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannelsContainer = 2,
AudioChannelPositions = "3/2/0.1",
AudioChannelPositionsTextContainer = null,
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
}
[Test]
public void should_format_8_channel_object_based_as_71_if_dtsx()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannelsContainer = 8,
AudioChannelsStream = 0,
AudioFormat = "DTS",
AudioAdditionalFeatures = "XLL X",
AudioChannelPositions = "Object Based",
AudioChannelPositionsTextContainer = "Object Based",
AudioChannelPositionsTextStream = "Object Based",
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
}
[Test]
public void should_format_8_channel_blank_as_71_if_dtsx()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannelsContainer = 8,
AudioChannelsStream = 0,
AudioFormat = "DTS",
AudioAdditionalFeatures = "XLL X",
AudioChannelPositions = "",
AudioChannelPositionsTextContainer = "",
AudioChannelPositionsTextStream = "Object Based",
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
}
[Test]
public void should_format_6_channel_zero_as_51_if_flac()
{
var mediaInfoModel = new MediaInfoModel
{
AudioFormat = "FLAC",
AudioChannelsContainer = 6,
AudioChannelPositions = "0/0/0",
AudioChannelPositionsTextContainer = null,
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
}
[Test]
public void should_ignore_culture_on_channel_summary()
{
Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");
var mediaInfoModel = new MediaInfoModel
{
AudioChannelsContainer = 2,
AudioChannelPositions = "3/2/0.1",
AudioChannelPositionsTextContainer = null,
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
}
[Test]
public void should_handle_AudioChannelPositions_three_digits()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannelsContainer = 2,
AudioChannelPositions = "3/2/0.2.1",
AudioChannelPositionsTextContainer = null,
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
}
[Test]
public void should_cleanup_extraneous_text_from_AudioChannelPositions()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannelsContainer = 2,
AudioChannelPositions = "Object Based / 3/2/2.1",
AudioChannelPositionsTextContainer = null,
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
}
[Test]
public void should_skip_empty_groups_in_AudioChannelPositions()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannelsContainer = 2,
AudioChannelPositions = " / 2/0/0.0",
AudioChannelPositionsTextContainer = null,
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
}
[Test]
public void should_sum_first_series_of_numbers_from_AudioChannelPositions()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannelsContainer = 2,
AudioChannelPositions = "3/2/2.1 / 3/2/2.1",
AudioChannelPositionsTextContainer = null,
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
}
[Test]
public void should_sum_first_series_of_numbers_from_AudioChannelPositions_with_three_digits()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannelsContainer = 2,
AudioChannelPositions = "3/2/0.2.1 / 3/2/0.1",
AudioChannelPositionsTextContainer = null,
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
}
[Test]
public void should_sum_dual_mono_representation_AudioChannelPositions()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannelsContainer = 2,
AudioChannelPositions = "1+1",
AudioChannelPositionsTextContainer = null,
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2.0m);
}
[Test]
public void should_use_AudioChannelPositionText_when_AudioChannelChannelPosition_is_invalid()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannelsContainer = 6,
AudioChannelPositions = "15 objects",
AudioChannelPositionsTextContainer = "15 objects / Front: L C R, Side: L R, LFE",
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
}
[Test]
public void should_remove_atmos_objects_from_AudioChannelPostions()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannelsContainer = 2,
AudioChannelPositions = "15 objects / 3/2.1",
AudioChannelPositionsTextContainer = null,
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
}
[Test]
public void should_use_audio_stream_text_when_exists()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannelsContainer = 6,
AudioChannelsStream = 8,
AudioChannelPositions = null,
AudioChannelPositionsTextContainer = null,
AudioChannelPositionsTextStream = "Front: L C R, Side: L R, Back: L R, LFE",
SchemaRevision = 6
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
}
[Test]
public void should_use_audio_stream_channels_when_exists()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannelsContainer = 6,
AudioChannelsStream = 8,
AudioChannelPositions = null,
AudioChannelPositionsTextContainer = null,
AudioChannelPositionsTextStream = null,
SchemaRevision = 6
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(8m);
}
}
}

View File

@@ -10,31 +10,45 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
{
private static string sceneName = "My.Series.S01E01-Sonarr";
[TestCase("mp2, , ", "droned.s01e03.swedish.720p.hdtv.x264-prince", "MP2")]
[TestCase("vorbis, , ", "DB Super HDTV", "Vorbis")]
[TestCase("pcm_s16le, , ", "DW DVDRip XviD-idTV, ", "PCM")]
[TestCase("truehd, , ", "", "TrueHD")]
[TestCase("truehd, , ", "TrueHD", "TrueHD")]
[TestCase("truehd, thd+, ", "Atmos", "TrueHD Atmos")]
[TestCase("truehd, thd+, ", "TrueHD.Atmos.7.1", "TrueHD Atmos")]
[TestCase("truehd, thd+, ", "", "TrueHD Atmos")]
[TestCase("wmav1, , ", "Droned.wmv", "WMA")]
[TestCase("wmav2, , ", "B.N.S04E18.720p.WEB-DL", "WMA")]
[TestCase("opus, , ", "Roadkill Ep3x11 - YouTube.webm", "Opus")]
[TestCase("mp3, , ", "climbing.mp4", "MP3")]
[TestCase("dts, , DTS-HD MA", "DTS-HD.MA", "DTS-HD MA")]
[TestCase("dts, , DTS:X", "DTS-X", "DTS-X")]
[TestCase("dts, , DTS-HD MA", "DTS-HD.MA", "DTS-HD MA")]
[TestCase("dts, , DTS-ES", "DTS-ES", "DTS-ES")]
[TestCase("dts, , DTS-ES", "DTS", "DTS-ES")]
[TestCase("dts, , DTS-HD HRA", "DTSHD-HRA", "DTS-HD HRA")]
[TestCase("dts, , DTS", "DTS", "DTS")]
[TestCase("eac3, ec+3, ", "EAC3.Atmos", "EAC3 Atmos")]
[TestCase("eac3, , ", "DDP5.1", "EAC3")]
[TestCase("ac3, , ", "DD5.1", "AC3")]
[TestCase("adpcm_ima_qt, , ", "Custom?", "PCM")]
[TestCase("adpcm_ms, , ", "Custom", "PCM")]
[TestCase("AC-3", "AC3")]
[TestCase("E-AC-3", "EAC3")]
[TestCase("MPEG Audio", "MPEG Audio")]
[TestCase("DTS", "DTS")]
public void should_format_audio_format_legacy(string audioFormat, string expectedFormat)
{
var mediaInfoModel = new MediaInfoModel
{
AudioFormat = audioFormat
};
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
}
[TestCase("MPEG Audio, A_MPEG/L2, , , ", "droned.s01e03.swedish.720p.hdtv.x264-prince", "MP2")]
[TestCase("Vorbis, A_VORBIS, , Xiph.Org libVorbis I 20101101 (Schaufenugget), ", "DB Super HDTV", "Vorbis")]
[TestCase("PCM, 1, , , ", "DW DVDRip XviD-idTV, ", "PCM")] // Dubbed most likely
[TestCase("TrueHD, A_TRUEHD, , , ", "", "TrueHD")]
[TestCase("MLP FBA, A_TRUEHD, , , ", "TrueHD", "TrueHD")]
[TestCase("MLP FBA, A_TRUEHD, , , 16-ch", "Atmos", "TrueHD Atmos")]
[TestCase("Atmos / TrueHD, A_TRUEHD, , , ", "TrueHD.Atmos.7.1", "TrueHD Atmos")]
[TestCase("Atmos / TrueHD / AC-3, 131, , , ", "", "TrueHD Atmos")]
[TestCase("WMA, 161, , , ", "Droned.wmv", "WMA")]
[TestCase("WMA, 162, Pro, , ", "B.N.S04E18.720p.WEB-DL", "WMA")]
[TestCase("Opus, A_OPUS, , , ", "Roadkill Ep3x11 - YouTube.webm", "Opus")]
[TestCase("mp3 , 0, , , ", "climbing.mp4", "MP3")]
[TestCase("DTS, A_DTS, , , XLL", "DTS-HD.MA", "DTS-HD MA")]
[TestCase("DTS, A_DTS, , , XLL X", "DTS-X", "DTS-X")]
[TestCase("DTS, A_DTS, , , ES XLL", "DTS-HD.MA", "DTS-HD MA")]
[TestCase("DTS, A_DTS, , , ES", "DTS-ES", "DTS-ES")]
[TestCase("DTS, A_DTS, , , ES XXCH", "DTS", "DTS-ES")]
[TestCase("DTS, A_DTS, , , XBR", "DTSHD-HRA", "DTS-HD HRA")]
[TestCase("DTS, A_DTS, , , DTS", "DTS", "DTS")]
[TestCase("E-AC-3, A_EAC3, , , JOC", "EAC3.Atmos", "EAC3 Atmos")]
[TestCase("E-AC-3, A_EAC3, , , ", "DDP5.1", "EAC3")]
[TestCase("AC-3, A_AC3, , , ", "DD5.1", "AC3")]
[TestCase("A_QUICKTIME, A_QUICKTIME, , , ", "", "")]
[TestCase("ADPCM, 2, , , ", "Custom?", "PCM")]
[TestCase("ADPCM, ima4, , , ", "Custom", "PCM")]
public void should_format_audio_format(string audioFormatPack, string sceneName, string expectedFormat)
{
var split = audioFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
@@ -42,12 +56,26 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
{
AudioFormat = split[0],
AudioCodecID = split[1],
AudioProfile = split[2]
AudioProfile = split[2],
AudioCodecLibrary = split[3],
AudioAdditionalFeatures = split[4]
};
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
}
[Test]
public void should_return_MP3_for_MPEG_Audio_with_Layer_3_for_the_profile()
{
var mediaInfoModel = new MediaInfoModel
{
AudioFormat = "MPEG Audio",
AudioProfile = "Layer 3"
};
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel, sceneName).Should().Be("MP3");
}
[Test]
public void should_return_AudioFormat_by_default()
{

View File

@@ -8,55 +8,94 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
[TestFixture]
public class FormatVideoCodecFixture : TestBase
{
[TestCase("mpeg2video, ", "Droned.S01E02.1080i.HDTV.DD5.1.MPEG2-NTb", "MPEG2")]
[TestCase("mpeg2video, ", "", "MPEG2")]
[TestCase("mpeg1video, ", "The.Simpsons.S13E04.INTERNAL-ANiVCD.mpg", "MPEG")]
[TestCase("vc1, WVC1", "B.N.S04E18.720p.WEB-DL", "VC1")]
[TestCase("vc1, V_MS/VFW/FOURCC/WVC1", "", "VC1")]
[TestCase("vc1, WMV3", "It's Always Sunny S07E13 The Gang's RevengeHDTV.XviD-2HD.avi", "VC1")]
[TestCase("h264, V.MPEG4/ISO/AVC", "pd.2015.S03E08.720p.iP.WEBRip.AAC2.0.H264-BTW", "h264")]
[TestCase("h264, V_MPEG4/ISO/AVC", "Resistance.2019.S01E03.1080p.RTE.WEB-DL.AAC2.0.x264-RTN", "x264")]
[TestCase("wmv1, WMV1", "Droned.wmv", "WMV")]
[TestCase("wmv2, WMV2", "Droned.wmv", "WMV")]
[TestCase("mpeg4, XVID", "", "XviD")]
[TestCase("mpeg4, DIV3", "spsm.dvdrip.divx.avi'.", "DivX")]
[TestCase("vp6, 4", "Top Gear - S12E01 - Lorries - SD TV.flv", "VP6")]
[TestCase("vp7, VP70", "Sweet Seymour.avi", "VP7")]
[TestCase("vp8, V_VP8", "Dick.mkv", "VP8")]
[TestCase("vp9, V_VP9", "Roadkill Ep3x11 - YouTube.webm", "VP9")]
[TestCase("h264, x264", "Ghost Advent - S04E05 - Stanley Hotel SDTV.avi", "x264")]
[TestCase("hevc, V_MPEGH/ISO/HEVC", "The BBT S11E12 The Matrimonial Metric 1080p 10bit AMZN WEB-DL", "h265")]
[TestCase("mpeg4, mp4v-20", "", "")]
[TestCase("mpeg4, XVID", "American.Chopper.S06E07.Mountain.Creek.Bike.DSR.XviD-KRS", "XviD")]
[TestCase("rpza, V_QUICKTIME", "Custom", "")]
[TestCase("mpeg4, FMP4", "", "")]
[TestCase("mpeg4, MP42", "", "")]
[TestCase("mpeg4, mp43", "Bubble.Guppies.S01E13.480p.WEB-DL.H.264-BTN-Custom", "")]
[TestCase("AVC", null, "x264")]
[TestCase("AVC", "source.title.x264.720p-Sonarr", "x264")]
[TestCase("AVC", "source.title.h264.720p-Sonarr", "h264")]
[TestCase("V_MPEGH/ISO/HEVC", null, "x265")]
[TestCase("V_MPEGH/ISO/HEVC", "source.title.x265.720p-Sonarr", "x265")]
[TestCase("V_MPEGH/ISO/HEVC", "source.title.h265.720p-Sonarr", "h265")]
[TestCase("MPEG-2 Video", null, "MPEG2")]
public void should_format_video_codec_with_source_title_legacy(string videoCodec, string sceneName, string expectedFormat)
{
var mediaInfoModel = new MediaInfoModel
{
VideoCodec = videoCodec
};
MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
}
[TestCase("MPEG Video, 2, Main@High, ", "Droned.S01E02.1080i.HDTV.DD5.1.MPEG2-NTb", "MPEG2")]
[TestCase("MPEG Video, V_MPEG2, Main@High, ", "", "MPEG2")]
[TestCase("MPEG Video, , , ", "The.Simpsons.S13E04.INTERNAL-ANiVCD.mpg", "MPEG")]
[TestCase("VC-1, WVC1, Advanced@L4, ", "B.N.S04E18.720p.WEB-DL", "VC1")]
[TestCase("VC-1, V_MS/VFW/FOURCC / WVC1, Advanced@L3, ", "", "VC1")]
[TestCase("VC-1, WMV3, MP@LL, ", "It's Always Sunny S07E13 The Gang's RevengeHDTV.XviD-2HD.avi", "VC1")]
[TestCase("V.MPEG4/ISO/AVC, V.MPEG4/ISO/AVC, , ", "pd.2015.S03E08.720p.iP.WEBRip.AAC2.0.H264-BTW", "h264")]
[TestCase("AVC / AVC, V_MPEG4/ISO/AVC, High@L4, ", "Resistance.2019.S01E03.1080p.RTE.WEB-DL.AAC2.0.x264-RTN", "x264")]
[TestCase("WMV1, WMV1, , ", "Droned.wmv", "WMV")]
[TestCase("WMV2, WMV2, , ", "Droned.wmv", "WMV")]
[TestCase("xvid, xvid, , ", "", "XviD")]
[TestCase("div3, div3, , ", "spsm.dvdrip.divx.avi'.", "DivX")]
[TestCase("VP6, 4, , ", "Top Gear - S12E01 - Lorries - SD TV.flv", "VP6")]
[TestCase("VP7, VP70, General, ", "Sweet Seymour.avi", "VP7")]
[TestCase("VP8, V_VP8, , ", "Dick.mkv", "VP8")]
[TestCase("VP9, V_VP9, , ", "Roadkill Ep3x11 - YouTube.webm", "VP9")]
[TestCase("x264, x264, , ", "Ghost Advent - S04E05 - Stanley Hotel SDTV.avi", "x264")]
[TestCase("V_MPEGH/ISO/HEVC, V_MPEGH/ISO/HEVC, , ", "The BBT S11E12 The Matrimonial Metric 1080p 10bit AMZN WEB-DL", "h265")]
[TestCase("MPEG-4 Visual, 20, Simple@L1, Lavc52.29.0", "Will.And.Grace.S08E14.WS.DVDrip.XviD.I.Love.L.Gay-Obfuscated", "XviD")]
[TestCase("MPEG-4 Visual, 20, Advanced Simple@L5, XviD0046", "", "XviD")]
[TestCase("MPEG-4 Visual, 20, , ", "", "")]
[TestCase("MPEG-4 Visual, mp4v-20, Simple@L1, Lavc57.48.101", "", "")]
[TestCase("mp4v, mp4v, , ", "American.Chopper.S06E07.Mountain.Creek.Bike.DSR.XviD-KRS", "XviD")]
[TestCase("V_QUICKTIME, V_QUICKTIME, , ", "Custom", "")]
[TestCase("MPEG-4 Visual, FMP4, , ", "", "")]
[TestCase("MPEG-4 Visual, MP42, , ", "", "")]
[TestCase("mp43, V_MS/VFW/FOURCC / mp43, , ", "Bubble.Guppies.S01E13.480p.WEB-DL.H.264-BTN-Custom", "")]
public void should_format_video_format(string videoFormatPack, string sceneName, string expectedFormat)
{
var split = videoFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
var mediaInfoModel = new MediaInfoModel
{
VideoFormat = split[0],
VideoCodecID = split[1]
VideoCodecID = split[1],
VideoProfile = split[2],
VideoCodecLibrary = split[3]
};
MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
}
[TestCase("h264, x264", "Some.Video.S01E01.h264", "x264")] // Force mediainfo tag
[TestCase("hevc, x265", "Some.Video.S01E01.h265", "x265")] // Force mediainfo tag
[TestCase("h264, ", "Some.Video.S01E01.x264", "x264")] // Not seen in practice, but honor tag if otherwise unknown
[TestCase("hevc, ", "Some.Video.S01E01.x265", "x265")] // Not seen in practice, but honor tag if otherwise unknown
[TestCase("h264, ", "Some.Video.S01E01", "h264")] // Default value
[TestCase("hevc, ", "Some.Video.S01E01", "h265")] // Default value
[TestCase("AVC, AVC, , x264", "Some.Video.S01E01.h264", "x264")] // Force mediainfo tag
[TestCase("HEVC, HEVC, , x265", "Some.Video.S01E01.h265", "x265")] // Force mediainfo tag
[TestCase("AVC, AVC, , ", "Some.Video.S01E01.x264", "x264")] // Not seen in practice, but honor tag if otherwise unknown
[TestCase("HEVC, HEVC, , ", "Some.Video.S01E01.x265", "x265")] // Not seen in practice, but honor tag if otherwise unknown
[TestCase("AVC, AVC, , ", "Some.Video.S01E01", "h264")] // Default value
[TestCase("HEVC, HEVC, , ", "Some.Video.S01E01", "h265")] // Default value
public void should_format_video_format_fallbacks(string videoFormatPack, string sceneName, string expectedFormat)
{
var split = videoFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
var mediaInfoModel = new MediaInfoModel
{
VideoFormat = split[0],
VideoCodecID = split[1]
VideoCodecID = split[1],
VideoProfile = split[2],
VideoCodecLibrary = split[3]
};
MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
}
[TestCase("MPEG-4 Visual, 20, , Intel(R) MPEG-4 encoder based on Intel(R) IPP 6.1 build 137.20[6.1.137.763]", "", "")]
public void should_warn_on_unknown_video_format(string videoFormatPack, string sceneName, string expectedFormat)
{
var split = videoFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
var mediaInfoModel = new MediaInfoModel
{
VideoFormat = split[0],
VideoCodecID = split[1],
VideoProfile = split[2],
VideoCodecLibrary = split[3]
};
MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);

View File

@@ -1,4 +1,4 @@
using FluentAssertions;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Test.Common;
@@ -8,18 +8,28 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
[TestFixture]
public class FormatVideoDynamicRangeFixture : TestBase
{
[TestCase(HdrFormat.None, "")]
[TestCase(HdrFormat.Hlg10, "HDR")]
[TestCase(HdrFormat.Pq10, "HDR")]
[TestCase(HdrFormat.Hdr10, "HDR")]
[TestCase(HdrFormat.Hdr10Plus, "HDR")]
[TestCase(HdrFormat.DolbyVision, "HDR")]
public void should_format_video_dynamic_range(HdrFormat format, string expectedVideoDynamicRange)
[TestCase(8, "", "", "", "", "")]
[TestCase(8, "BT.601 NTSC", "BT.709", "", "", "")]
[TestCase(10, "BT.2020", "PQ", "", "", "HDR")]
[TestCase(8, "BT.2020", "PQ", "", "", "")]
[TestCase(10, "BT.601 NTSC", "PQ", "", "", "")]
[TestCase(10, "BT.2020", "BT.709", "", "", "")]
[TestCase(10, "BT.2020", "HLG", "", "", "HDR")]
[TestCase(10, "", "", "Dolby Vision", "", "HDR")]
[TestCase(10, "", "", "SMPTE ST 2086", "HDR10", "HDR")]
[TestCase(8, "", "", "Dolby Vision", "", "HDR")]
[TestCase(8, "", "", "SMPTE ST 2086", "HDR10", "HDR")]
[TestCase(10, "BT.2020", "PQ", "Dolby Vision / SMPTE ST 2086", "Blu-ray / HDR10", "HDR")]
public void should_format_video_dynamic_range(int bitDepth, string colourPrimaries, string transferCharacteristics, string hdrFormat, string hdrFormatCompatibility, string expectedVideoDynamicRange)
{
var mediaInfo = new MediaInfoModel
{
VideoHdrFormat = format,
SchemaRevision = 8
VideoBitDepth = bitDepth,
VideoColourPrimaries = colourPrimaries,
VideoTransferCharacteristics = transferCharacteristics,
VideoHdrFormat = hdrFormat,
VideoHdrFormatCompatibility = hdrFormatCompatibility,
SchemaRevision = 7
};
MediaInfoFormatter.FormatVideoDynamicRange(mediaInfo).Should().Be(expectedVideoDynamicRange);

View File

@@ -1,13 +1,8 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using FFMpegCore;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common.Categories;
@@ -45,26 +40,33 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
var info = Subject.GetMediaInfo(path);
info.VideoFormat.Should().Be("h264");
info.VideoCodec.Should().BeNull();
info.VideoFormat.Should().Be("AVC");
info.VideoCodecID.Should().Be("avc1");
info.VideoProfile.Should().Be("Constrained Baseline");
info.AudioFormat.Should().Be("aac");
info.AudioCodecID.Should().Be("mp4a");
info.AudioProfile.Should().Be("LC");
info.AudioBitrate.Should().Be(125488);
info.AudioChannels.Should().Be(2);
info.AudioChannelPositions.Should().Be("stereo");
info.AudioLanguages.Should().BeEquivalentTo("eng");
info.VideoProfile.Should().Be("Baseline@L2.1");
info.VideoCodecLibrary.Should().Be("");
info.AudioFormat.Should().Be("AAC");
info.AudioCodecID.Should().BeOneOf("40", "mp4a-40-2");
info.AudioProfile.Should().BeOneOf("", "LC");
info.AudioCodecLibrary.Should().Be("");
info.AudioBitrate.Should().Be(128000);
info.AudioChannelsContainer.Should().Be(2);
info.AudioChannelsStream.Should().Be(0);
info.AudioChannelPositionsTextContainer.Should().Be("Front: L R");
info.AudioChannelPositionsTextStream.Should().Be("");
info.AudioLanguages.Should().Be("English");
info.Height.Should().Be(320);
info.RunTime.Seconds.Should().Be(10);
info.ScanType.Should().Be("Progressive");
info.Subtitles.Should().BeEmpty();
info.VideoBitrate.Should().Be(193328);
info.Subtitles.Should().Be("");
info.VideoBitrate.Should().Be(193329);
info.VideoFps.Should().Be(24);
info.Width.Should().Be(480);
info.VideoBitDepth.Should().Be(8);
info.VideoColourPrimaries.Should().Be("smpte170m");
info.VideoTransferCharacteristics.Should().Be("bt709");
info.VideoColourPrimaries.Should().Be("BT.601 NTSC");
info.VideoTransferCharacteristics.Should().Be("BT.709");
info.AudioAdditionalFeatures.Should().BeOneOf("", "LC");
info.VideoHdrFormat.Should().BeEmpty();
info.VideoHdrFormatCompatibility.Should().BeEmpty();
}
[Test]
@@ -81,48 +83,45 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
var info = Subject.GetMediaInfo(path);
info.VideoFormat.Should().Be("h264");
info.VideoCodec.Should().BeNull();
info.VideoFormat.Should().Be("AVC");
info.VideoCodecID.Should().Be("avc1");
info.VideoProfile.Should().Be("Constrained Baseline");
info.AudioFormat.Should().Be("aac");
info.AudioCodecID.Should().Be("mp4a");
info.AudioProfile.Should().Be("LC");
info.AudioBitrate.Should().Be(125488);
info.AudioChannels.Should().Be(2);
info.AudioChannelPositions.Should().Be("stereo");
info.AudioLanguages.Should().BeEquivalentTo("eng");
info.VideoProfile.Should().Be("Baseline@L2.1");
info.VideoCodecLibrary.Should().Be("");
info.AudioFormat.Should().Be("AAC");
info.AudioCodecID.Should().BeOneOf("40", "mp4a-40-2");
info.AudioProfile.Should().BeOneOf("", "LC");
info.AudioCodecLibrary.Should().Be("");
info.AudioBitrate.Should().Be(128000);
info.AudioChannelsContainer.Should().Be(2);
info.AudioChannelsStream.Should().Be(0);
info.AudioChannelPositionsTextContainer.Should().Be("Front: L R");
info.AudioChannelPositionsTextStream.Should().Be("");
info.AudioLanguages.Should().Be("English");
info.Height.Should().Be(320);
info.RunTime.Seconds.Should().Be(10);
info.ScanType.Should().Be("Progressive");
info.Subtitles.Should().BeEmpty();
info.VideoBitrate.Should().Be(193328);
info.Subtitles.Should().Be("");
info.VideoBitrate.Should().Be(193329);
info.VideoFps.Should().Be(24);
info.Width.Should().Be(480);
info.VideoColourPrimaries.Should().Be("smpte170m");
info.VideoTransferCharacteristics.Should().Be("bt709");
info.VideoColourPrimaries.Should().Be("BT.601 NTSC");
info.VideoTransferCharacteristics.Should().Be("BT.709");
info.AudioAdditionalFeatures.Should().BeOneOf("", "LC");
info.VideoHdrFormat.Should().BeEmpty();
info.VideoHdrFormatCompatibility.Should().BeEmpty();
}
[TestCase(8, "", "", "", HdrFormat.None)]
[TestCase(10, "", "", "", HdrFormat.None)]
[TestCase(10, "bt709", "bt709", "", HdrFormat.None)]
[TestCase(8, "bt2020", "smpte2084", "", HdrFormat.None)]
[TestCase(10, "bt2020", "bt2020-10", "", HdrFormat.Hlg10)]
[TestCase(10, "bt2020", "arib-std-b67", "", HdrFormat.Hlg10)]
[TestCase(10, "bt2020", "smpte2084", "", HdrFormat.Pq10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.SideData", HdrFormat.Pq10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.MasteringDisplayMetadata", HdrFormat.Hdr10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.ContentLightLevelMetadata", HdrFormat.Hdr10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.HdrDynamicMetadataSpmte2094", HdrFormat.Hdr10Plus)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", HdrFormat.DolbyVision)]
public void should_detect_hdr_correctly(int bitDepth, string colourPrimaries, string transferFunction, string sideDataTypes, HdrFormat expected)
[Test]
public void should_dispose_file_after_scanning_mediainfo()
{
var assembly = Assembly.GetAssembly(typeof(FFProbe));
var types = sideDataTypes.Split(",").Select(x => x.Trim()).ToList();
var sideData = types.Where(x => x.IsNotNullOrWhiteSpace()).Select(x => assembly.CreateInstance(x)).Cast<SideData>().ToList();
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Media", "H264_sample.mp4");
var result = VideoFileInfoReader.GetHdrFormat(bitDepth, colourPrimaries, transferFunction, sideData);
var info = Subject.GetMediaInfo(path);
result.Should().Be(expected);
var stream = new FileStream(path, FileMode.Open, FileAccess.Write);
stream.Close();
}
}
}

View File

@@ -1,4 +1,3 @@
using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
@@ -26,7 +25,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators.Augm
public void should_return_language_for_single_known_language()
{
var mediaInfo = Builder<MediaInfoModel>.CreateNew()
.With(m => m.AudioLanguages = new List<string> { "eng" })
.With(m => m.AudioLanguages = "English")
.Build();
var localMovie = Builder<LocalMovie>.CreateNew()
@@ -43,7 +42,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators.Augm
public void should_only_return_one_when_language_duplicated()
{
var mediaInfo = Builder<MediaInfoModel>.CreateNew()
.With(m => m.AudioLanguages = new List<string> { "eng", "eng" })
.With(m => m.AudioLanguages = "English / English")
.Build();
var localMovie = Builder<LocalMovie>.CreateNew()
@@ -60,7 +59,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators.Augm
public void should_return_null_if_all_unknown()
{
var mediaInfo = Builder<MediaInfoModel>.CreateNew()
.With(m => m.AudioLanguages = new List<string> { "pirate", "pirate" })
.With(m => m.AudioLanguages = "Pirate / Pirate")
.Build();
var localMovie = Builder<LocalMovie>.CreateNew()
@@ -76,7 +75,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators.Augm
public void should_return_known_languages_only()
{
var mediaInfo = Builder<MediaInfoModel>.CreateNew()
.With(m => m.AudioLanguages = new List<string> { "eng", "pirate" })
.With(m => m.AudioLanguages = "English / Pirate")
.Build();
var localMovie = Builder<LocalMovie>.CreateNew()
@@ -93,7 +92,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators.Augm
public void should_return_multiple_known_languages()
{
var mediaInfo = Builder<MediaInfoModel>.CreateNew()
.With(m => m.AudioLanguages = new List<string> { "eng", "ger" })
.With(m => m.AudioLanguages = "English / German")
.Build();
var localMovie = Builder<LocalMovie>.CreateNew()

View File

@@ -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>

View File

@@ -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
};

View File

@@ -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);
}
}
}

View File

@@ -65,7 +65,7 @@ namespace NzbDrone.Core.Test.NotificationTests
[Test]
public void should_add_collection_movie_if_valid_mediainfo()
{
GiventValidMediaInfo(Quality.Bluray1080p, "5.1", "DTS", "Progressive");
GiventValidMediaInfo(Quality.Bluray1080p, "3/2/0.1", "DTS", "Interlaced");
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile);
@@ -73,7 +73,7 @@ namespace NzbDrone.Core.Test.NotificationTests
.Verify(v => v.AddToCollection(It.Is<TraktCollectMoviesResource>(t =>
t.Movies.First().Audio == "dts" &&
t.Movies.First().AudioChannels == "5.1" &&
t.Movies.First().Resolution == "hd_1080p" &&
t.Movies.First().Resolution == "hd_1080i" &&
t.Movies.First().MediaType == "bluray"),
It.IsAny<string>()), Times.Once());
}
@@ -81,7 +81,7 @@ namespace NzbDrone.Core.Test.NotificationTests
[Test]
public void should_format_audio_channels_to_one_decimal_when_adding_collection_movie()
{
GiventValidMediaInfo(Quality.Bluray1080p, "2.0", "DTS", "Progressive");
GiventValidMediaInfo(Quality.Bluray1080p, "2/0/0", "DTS", "Interlaced");
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile);
@@ -89,7 +89,7 @@ namespace NzbDrone.Core.Test.NotificationTests
.Verify(v => v.AddToCollection(It.Is<TraktCollectMoviesResource>(t =>
t.Movies.First().Audio == "dts" &&
t.Movies.First().AudioChannels == "2.0" &&
t.Movies.First().Resolution == "hd_1080p" &&
t.Movies.First().Resolution == "hd_1080i" &&
t.Movies.First().MediaType == "bluray"),
It.IsAny<string>()), Times.Once());
}

View File

@@ -19,7 +19,6 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
{
[Platform(Exclude = "Win")]
[TestFixture]
public class FileNameBuilderFixture : CoreTest<FileNameBuilder>
@@ -369,44 +368,35 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
_movieFile.MediaInfo = new MediaInfoModel()
{
VideoFormat = "h264",
AudioFormat = "dts",
AudioLanguages = new List<string> { "eng", "spa" },
Subtitles = new List<string> { "eng", "spa", "ita" }
VideoFormat = "AVC",
AudioFormat = "DTS",
AudioLanguages = "English/Spanish",
Subtitles = "English/Spanish/Italian"
};
Subject.BuildFileName(_movie, _movieFile)
.Should().Be("South.Park.H264.DTS[EN+ES].[EN+ES+IT]");
}
[TestCase("nob", "NB")]
[TestCase("swe", "SV")]
[TestCase("zho", "ZH")]
[TestCase("chi", "ZH")]
[TestCase("fre", "FR")]
[TestCase("rum", "RO")]
[TestCase("per", "FA")]
[TestCase("ger", "DE")]
[TestCase("cze", "CS")]
[TestCase("ice", "IS")]
[TestCase("dut", "NL")]
[TestCase("nor", "NO")]
[TestCase("Norwegian Bokmal", "NB")]
[TestCase("Swedis", "SV")]
[TestCase("Chinese", "ZH")]
public void should_format_languagecodes_properly(string language, string code)
{
_namingConfig.StandardMovieFormat = "{Movie.Title}.{MEDIAINFO.FULL}";
_movieFile.MediaInfo = new MediaInfoModel()
{
VideoFormat = "h264",
AudioFormat = "dts",
AudioChannels = 6,
AudioLanguages = new List<string> { "eng" },
Subtitles = new List<string> { language },
VideoCodec = "AVC",
AudioFormat = "DTS",
AudioChannelsContainer = 6,
AudioLanguages = "English",
Subtitles = language,
SchemaRevision = 3
};
Subject.BuildFileName(_movie, _movieFile)
.Should().Be($"South.Park.H264.DTS.[{code}]");
.Should().Be($"South.Park.X264.DTS.[{code}]");
}
[Test]
@@ -416,17 +406,16 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
_movieFile.MediaInfo = new MediaInfoModel()
{
VideoFormat = "h264",
AudioFormat = "dts",
AudioLanguages = new List<string> { "eng" },
Subtitles = new List<string> { "eng", "spa", "ita" }
VideoFormat = "AVC",
AudioFormat = "DTS",
AudioLanguages = "English",
Subtitles = "English/Spanish/Italian"
};
Subject.BuildFileName(_movie, _movieFile)
.Should().Be("South.Park.H264.DTS.[EN+ES+IT]");
}
[Ignore("not currently supported")]
[Test]
public void should_format_mediainfo_3d_properly()
{
@@ -434,11 +423,11 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
_movieFile.MediaInfo = new MediaInfoModel()
{
VideoFormat = "h264",
VideoFormat = "AVC",
VideoMultiViewCount = 2,
AudioFormat = "dts",
AudioLanguages = new List<string> { "eng" },
Subtitles = new List<string> { "eng", "spa", "ita" }
AudioFormat = "DTS",
AudioLanguages = "English",
Subtitles = "English/Spanish/Italian"
};
Subject.BuildFileName(_movie, _movieFile)
@@ -642,8 +631,8 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
.Should().Be(releaseGroup);
}
[TestCase("eng", "")]
[TestCase("eng/deu", "[EN+DE]")]
[TestCase("English", "")]
[TestCase("English/German", "[EN+DE]")]
public void should_format_audio_languages(string audioLanguages, string expected)
{
_movieFile.ReleaseGroup = null;
@@ -656,8 +645,8 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
.Should().Be(expected);
}
[TestCase("eng", "[EN]")]
[TestCase("eng/deu", "[EN+DE]")]
[TestCase("English", "[EN]")]
[TestCase("English/German", "[EN+DE]")]
public void should_format_audio_languages_all(string audioLanguages, string expected)
{
_movieFile.ReleaseGroup = null;
@@ -670,15 +659,19 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
.Should().Be(expected);
}
[TestCase(HdrFormat.None, "South.Park")]
[TestCase(HdrFormat.Hlg10, "South.Park.HDR")]
[TestCase(HdrFormat.Hdr10, "South.Park.HDR")]
public void should_include_hdr_for_mediainfo_videodynamicrange_with_valid_properties(HdrFormat hdrFormat, string expectedName)
[TestCase(8, "BT.601 NTSC", "BT.709", "South.Park")]
[TestCase(10, "BT.2020", "PQ", "South.Park.HDR")]
[TestCase(10, "BT.2020", "HLG", "South.Park.HDR")]
[TestCase(0, null, null, "South.Park")]
public void should_include_hdr_for_mediainfo_videodynamicrange_with_valid_properties(int bitDepth,
string colourPrimaries,
string transferCharacteristics,
string expectedName)
{
_namingConfig.StandardMovieFormat =
"{Movie.Title}.{MediaInfo VideoDynamicRange}";
GivenMediaInfoModel(hdrFormat: hdrFormat);
GivenMediaInfoModel(videoBitDepth: bitDepth, videoColourPrimaries: colourPrimaries, videoTransferCharacteristics: transferCharacteristics);
Subject.BuildFileName(_movie, _movieFile)
.Should().Be(expectedName);
@@ -750,24 +743,26 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
Mocker.GetMock<IUpdateMediaInfo>().Verify(v => v.Update(_movieFile, _movie), Times.Never());
}
private void GivenMediaInfoModel(string videoCodec = "h264",
string audioCodec = "dts",
private void GivenMediaInfoModel(string videoCodec = "AVC",
string audioCodec = "DTS",
int audioChannels = 6,
int videoBitDepth = 8,
HdrFormat hdrFormat = HdrFormat.None,
string audioLanguages = "eng",
string subtitles = "eng/spa/ita",
string videoColourPrimaries = "",
string videoTransferCharacteristics = "",
string audioLanguages = "English",
string subtitles = "English/Spanish/Italian",
int schemaRevision = 5)
{
_movieFile.MediaInfo = new MediaInfoModel
{
VideoFormat = videoCodec,
VideoCodec = videoCodec,
AudioFormat = audioCodec,
AudioChannels = audioChannels,
AudioLanguages = audioLanguages.Split("/").ToList(),
Subtitles = subtitles.Split("/").ToList(),
AudioChannelsContainer = audioChannels,
AudioLanguages = audioLanguages,
Subtitles = subtitles,
VideoBitDepth = videoBitDepth,
VideoHdrFormat = hdrFormat,
VideoColourPrimaries = videoColourPrimaries,
VideoTransferCharacteristics = videoTransferCharacteristics,
SchemaRevision = schemaRevision
};
}

View File

@@ -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);
}

View File

@@ -61,9 +61,12 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("World.Movie.Z.2.EXTENDED.2013.German.DL.1080p.BluRay.AVC-XANOR", "World Movie Z 2")]
[TestCase("G.I.Movie.Movie.2013.THEATRiCAL.COMPLETE.BLURAY-GLiMMER", "G.I. Movie Movie")]
[TestCase("www.Torrenting.org - Movie.2008.720p.X264-DIMENSION", "Movie")]
//Dont think this will ever apply but adding it to keep the regex in line with upstream
[TestCase("Movie name: 2013.THEATRiCAL.COMPLETE.BLURAY-GLiMMER", "Movie name")]
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 +102,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)]

View File

@@ -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)

View File

@@ -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]

View File

@@ -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]

View File

@@ -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);

View File

@@ -118,7 +118,6 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Some.Movie.2019.1080p.BDRip.X264.AC3-EVO-4P", "EVO")]
[TestCase("Some.Movie.2019.1080p.BDRip.X264.AC3-EVO-4Planet", "EVO")]
[TestCase("Some.Movie.2019.1080p.BDRip.X264.AC3-DON-AlteZachen", "DON")]
[TestCase("Some.Movie.2019.1080p.BDRip.X264.AC3-HarrHD-RePACKPOST", "HarrHD")]
public void should_not_include_bad_suffix_in_release_group(string title, string expected)
{

View File

@@ -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" />

View File

@@ -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)]

View File

@@ -17,7 +17,6 @@ namespace NzbDrone.Core.Blocklisting
bool Blocklisted(int movieId, ReleaseInfo release);
PagingSpec<Blocklist> Paged(PagingSpec<Blocklist> pagingSpec);
List<Blocklist> GetByMovieId(int movieId);
void Block(RemoteMovie remoteMovie, string message);
void Delete(int id);
void Delete(List<int> ids);
}
@@ -73,30 +72,6 @@ namespace NzbDrone.Core.Blocklisting
return _blocklistRepository.BlocklistedByMovie(movieId);
}
public void Block(RemoteMovie remoteMovie, string message)
{
var blocklist = new Blocklist
{
MovieId = remoteMovie.Movie.Id,
SourceTitle = remoteMovie.Release.Title,
Quality = remoteMovie.ParsedMovieInfo.Quality,
Date = DateTime.UtcNow,
PublishedDate = remoteMovie.Release.PublishDate,
Size = remoteMovie.Release.Size,
Indexer = remoteMovie.Release.Indexer,
Protocol = remoteMovie.Release.DownloadProtocol,
Message = message,
Languages = remoteMovie.ParsedMovieInfo.Languages
};
if (remoteMovie.Release is TorrentInfo torrentRelease)
{
blocklist.TorrentInfoHash = torrentRelease.InfoHash;
}
_blocklistRepository.Insert(blocklist);
}
public void Delete(int id)
{
_blocklistRepository.Delete(id);

View File

@@ -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); }

View File

@@ -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; }

View File

@@ -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,

View File

@@ -1,903 +0,0 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using Dapper;
using FluentMigrator;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration.Framework;
using NzbDrone.Core.MediaFiles.MediaInfo;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(199)]
public class mediainfo_to_ffmpeg : NzbDroneMigrationBase
{
private readonly JsonSerializerOptions _serializerSettings;
public mediainfo_to_ffmpeg()
{
var serializerSettings = new JsonSerializerOptions
{
AllowTrailingCommas = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNameCaseInsensitive = true,
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
serializerSettings.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, true));
serializerSettings.Converters.Add(new STJTimeSpanConverter());
serializerSettings.Converters.Add(new STJUtcConverter());
_serializerSettings = serializerSettings;
}
protected override void MainDbUpgrade()
{
Execute.WithConnection(MigrateToFfprobe);
}
private void MigrateToFfprobe(IDbConnection conn, IDbTransaction tran)
{
var existing = conn.Query<MediaInfoRaw>("SELECT Id, MediaInfo, SceneName FROM MovieFiles");
var updated = new List<MediaInfoRaw>();
foreach (var row in existing)
{
if (row.MediaInfo.IsNullOrWhiteSpace())
{
continue;
}
// basic parse to check schema revision
// in case user already tested ffmpeg branch
var mediaInfoVersion = JsonSerializer.Deserialize<MediaInfoBase>(row.MediaInfo, _serializerSettings);
if (mediaInfoVersion.SchemaRevision >= 8)
{
continue;
}
// parse and migrate
var mediaInfo = JsonSerializer.Deserialize<MediaInfo198>(row.MediaInfo, _serializerSettings);
var ffprobe = MigrateMediaInfo(mediaInfo, row.SceneName);
updated.Add(new MediaInfoRaw
{
Id = row.Id,
MediaInfo = JsonSerializer.Serialize(ffprobe, _serializerSettings)
});
}
var updateSql = "UPDATE MovieFiles SET MediaInfo = @MediaInfo WHERE Id = @Id";
conn.Execute(updateSql, updated, transaction: tran);
}
public MediaInfo199 MigrateMediaInfo(MediaInfo198 old, string sceneName)
{
var m = new MediaInfo199
{
SchemaRevision = old.SchemaRevision,
ContainerFormat = old.ContainerFormat,
VideoProfile = old.VideoProfile,
VideoBitrate = old.VideoBitrate,
VideoBitDepth = old.VideoBitDepth,
VideoMultiViewCount = old.VideoMultiViewCount,
VideoColourPrimaries = MigratePrimaries(old.VideoColourPrimaries),
VideoTransferCharacteristics = MigrateTransferCharacteristics(old.VideoTransferCharacteristics),
Height = old.Height,
Width = old.Width,
AudioBitrate = old.AudioBitrate,
RunTime = old.RunTime,
AudioStreamCount = old.AudioStreamCount,
VideoFps = old.VideoFps,
ScanType = old.ScanType,
AudioLanguages = MigrateLanguages(old.AudioLanguages),
Subtitles = MigrateLanguages(old.Subtitles)
};
m.VideoHdrFormat = MigrateHdrFormat(old);
MigrateVideoCodec(old, m, sceneName);
MigrateAudioCodec(old, m);
MigrateAudioChannelPositions(old, m);
m.AudioChannels = old.AudioChannelsStream > 0 ? old.AudioChannelsStream : old.AudioChannelsContainer;
return m;
}
private void MigrateVideoCodec(MediaInfo198 mediaInfo, MediaInfo199 m, string sceneName)
{
if (mediaInfo.VideoFormat == null)
{
MigrateVideoCodecLegacy(mediaInfo, m, sceneName);
return;
}
var videoFormat = mediaInfo.VideoFormat.Trim().Split(new[] { " / " }, StringSplitOptions.RemoveEmptyEntries);
var videoCodecID = mediaInfo.VideoCodecID ?? string.Empty;
var videoCodecLibrary = mediaInfo.VideoCodecLibrary ?? string.Empty;
var result = mediaInfo.VideoFormat.Trim();
m.VideoFormat = result;
m.VideoCodecID = null;
if (videoFormat.ContainsIgnoreCase("x264"))
{
m.VideoFormat = "h264";
m.VideoCodecID = "x264";
return;
}
if (videoFormat.ContainsIgnoreCase("AVC") || videoFormat.ContainsIgnoreCase("V.MPEG4/ISO/AVC"))
{
m.VideoFormat = "h264";
if (videoCodecLibrary.StartsWithIgnoreCase("x264"))
{
m.VideoCodecID = "x264";
}
return;
}
if (videoFormat.ContainsIgnoreCase("HEVC") || videoFormat.ContainsIgnoreCase("V_MPEGH/ISO/HEVC"))
{
m.VideoFormat = "hevc";
if (videoCodecLibrary.StartsWithIgnoreCase("x265"))
{
m.VideoCodecID = "x265";
}
return;
}
if (videoFormat.ContainsIgnoreCase("MPEG Video"))
{
if (videoCodecID == "2" || videoCodecID == "V_MPEG2")
{
m.VideoFormat = "mpeg2video";
}
if (videoCodecID.IsNullOrWhiteSpace())
{
m.VideoFormat = "MPEG";
}
}
if (videoFormat.ContainsIgnoreCase("MPEG-2 Video"))
{
m.VideoFormat = "mpeg2video";
}
if (videoFormat.ContainsIgnoreCase("MPEG-4 Visual"))
{
m.VideoFormat = "mpeg4";
if (videoCodecID.ContainsIgnoreCase("XVID") ||
videoCodecLibrary.StartsWithIgnoreCase("XviD"))
{
m.VideoCodecID = "XVID";
}
if (videoCodecID.ContainsIgnoreCase("DIV3") ||
videoCodecID.ContainsIgnoreCase("DIVX") ||
videoCodecID.ContainsIgnoreCase("DX50") ||
videoCodecLibrary.StartsWithIgnoreCase("DivX"))
{
m.VideoCodecID = "DIVX";
}
return;
}
if (videoFormat.ContainsIgnoreCase("MPEG-4 Visual") || videoFormat.ContainsIgnoreCase("mp4v"))
{
m.VideoFormat = "mpeg4";
result = GetSceneNameMatch(sceneName, "XviD", "DivX", "");
if (result == "XviD")
{
m.VideoCodecID = "XVID";
}
if (result == "DivX")
{
m.VideoCodecID = "DIVX";
}
return;
}
if (videoFormat.ContainsIgnoreCase("VC-1"))
{
m.VideoFormat = "vc1";
return;
}
if (videoFormat.ContainsIgnoreCase("AV1"))
{
m.VideoFormat = "av1";
return;
}
if (videoFormat.ContainsIgnoreCase("VP6") || videoFormat.ContainsIgnoreCase("VP7") ||
videoFormat.ContainsIgnoreCase("VP8") || videoFormat.ContainsIgnoreCase("VP9"))
{
m.VideoFormat = videoFormat.First().ToLowerInvariant();
return;
}
if (videoFormat.ContainsIgnoreCase("WMV1") || videoFormat.ContainsIgnoreCase("WMV2"))
{
m.VideoFormat = "WMV";
return;
}
if (videoFormat.ContainsIgnoreCase("DivX") || videoFormat.ContainsIgnoreCase("div3"))
{
m.VideoFormat = "mpeg4";
m.VideoCodecID = "DIVX";
return;
}
if (videoFormat.ContainsIgnoreCase("XviD"))
{
m.VideoFormat = "mpeg4";
m.VideoCodecID = "XVID";
return;
}
if (videoFormat.ContainsIgnoreCase("V_QUICKTIME") ||
videoFormat.ContainsIgnoreCase("RealVideo 4"))
{
m.VideoFormat = "qtrle";
return;
}
if (videoFormat.ContainsIgnoreCase("mp42") ||
videoFormat.ContainsIgnoreCase("mp43"))
{
m.VideoFormat = "mpeg4";
return;
}
}
private void MigrateVideoCodecLegacy(MediaInfo198 mediaInfo, MediaInfo199 m, string sceneName)
{
var videoCodec = mediaInfo.VideoCodec;
m.VideoFormat = videoCodec;
m.VideoCodecID = null;
if (videoCodec.IsNullOrWhiteSpace())
{
m.VideoFormat = null;
return;
}
if (videoCodec == "AVC")
{
m.VideoFormat = "h264";
}
if (videoCodec == "V_MPEGH/ISO/HEVC" || videoCodec == "HEVC")
{
m.VideoFormat = "hevc";
}
if (videoCodec == "MPEG-2 Video")
{
m.VideoFormat = "mpeg2video";
}
if (videoCodec == "MPEG-4 Visual")
{
var result = GetSceneNameMatch(sceneName, "DivX", "XviD");
if (result == "DivX")
{
m.VideoFormat = "mpeg4";
m.VideoCodecID = "DIVX";
}
m.VideoFormat = "mpeg4";
m.VideoCodecID = "XVID";
}
if (videoCodec.StartsWithIgnoreCase("XviD"))
{
m.VideoFormat = "mpeg4";
m.VideoCodecID = "XVID";
}
if (videoCodec.StartsWithIgnoreCase("DivX"))
{
m.VideoFormat = "mpeg4";
m.VideoCodecID = "DIVX";
}
if (videoCodec.EqualsIgnoreCase("VC-1"))
{
m.VideoFormat = "vc1";
}
}
private HdrFormat MigrateHdrFormat(MediaInfo198 mediaInfo)
{
if (mediaInfo.VideoHdrFormatCompatibility.IsNotNullOrWhiteSpace())
{
if (mediaInfo.VideoHdrFormatCompatibility.ContainsIgnoreCase("HLG"))
{
return HdrFormat.Hlg10;
}
if (mediaInfo.VideoHdrFormatCompatibility.ContainsIgnoreCase("dolby"))
{
return HdrFormat.DolbyVision;
}
if (mediaInfo.VideoHdrFormatCompatibility.ContainsIgnoreCase("dolby"))
{
return HdrFormat.DolbyVision;
}
if (mediaInfo.VideoHdrFormatCompatibility.ContainsIgnoreCase("hdr10+"))
{
return HdrFormat.Hdr10Plus;
}
if (mediaInfo.VideoHdrFormatCompatibility.ContainsIgnoreCase("hdr10"))
{
return HdrFormat.Hdr10;
}
}
return VideoFileInfoReader.GetHdrFormat(mediaInfo.VideoBitDepth, mediaInfo.VideoColourPrimaries, mediaInfo.VideoTransferCharacteristics, new ());
}
private void MigrateAudioCodec(MediaInfo198 mediaInfo, MediaInfo199 m)
{
if (mediaInfo.AudioCodecID == null)
{
MigrateAudioCodecLegacy(mediaInfo, m);
return;
}
var audioFormat = mediaInfo.AudioFormat.Trim().Split(new[] { " / " }, StringSplitOptions.RemoveEmptyEntries);
var audioCodecID = mediaInfo.AudioCodecID ?? string.Empty;
var splitAdditionalFeatures = (mediaInfo.AudioAdditionalFeatures ?? string.Empty).Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
m.AudioFormat = "";
if (audioFormat.Empty())
{
return;
}
if (audioFormat.ContainsIgnoreCase("Atmos"))
{
m.AudioFormat = "truehd";
m.AudioCodecID = "thd+";
return;
}
if (audioFormat.ContainsIgnoreCase("MLP FBA"))
{
m.AudioFormat = "truehd";
if (splitAdditionalFeatures.ContainsIgnoreCase("16-ch"))
{
m.AudioCodecID = "thd+";
return;
}
return;
}
if (audioFormat.ContainsIgnoreCase("TrueHD"))
{
m.AudioFormat = "truehd";
return;
}
if (audioFormat.ContainsIgnoreCase("FLAC"))
{
m.AudioFormat = "flac";
return;
}
if (audioFormat.ContainsIgnoreCase("DTS"))
{
m.AudioFormat = "dts";
if (splitAdditionalFeatures.ContainsIgnoreCase("XLL"))
{
if (splitAdditionalFeatures.ContainsIgnoreCase("X"))
{
m.AudioProfile = "DTS:X";
return;
}
m.AudioProfile = "DTS-HD MA";
return;
}
if (splitAdditionalFeatures.ContainsIgnoreCase("ES"))
{
m.AudioProfile = "DTS-ES";
return;
}
if (splitAdditionalFeatures.ContainsIgnoreCase("XBR"))
{
m.AudioProfile = "DTS-HD HRA";
return;
}
return;
}
if (audioFormat.ContainsIgnoreCase("E-AC-3"))
{
m.AudioFormat = "eac3";
if (splitAdditionalFeatures.ContainsIgnoreCase("JOC"))
{
m.AudioCodecID = "ec+3";
}
return;
}
if (audioFormat.ContainsIgnoreCase("AC-3"))
{
m.AudioFormat = "ac3";
return;
}
if (audioFormat.ContainsIgnoreCase("AAC"))
{
m.AudioFormat = "aac";
if (audioCodecID == "A_AAC/MPEG4/LC/SBR")
{
m.AudioCodecID = audioCodecID;
}
return;
}
if (audioFormat.ContainsIgnoreCase("mp3"))
{
m.AudioFormat = "mp3";
return;
}
if (audioFormat.ContainsIgnoreCase("MPEG Audio"))
{
if (mediaInfo.AudioCodecID == "55" || mediaInfo.AudioCodecID == "A_MPEG/L3" || mediaInfo.AudioProfile == "Layer 3")
{
m.AudioFormat = "mp3";
return;
}
if (mediaInfo.AudioCodecID == "A_MPEG/L2" || mediaInfo.AudioProfile == "Layer 2")
{
m.AudioFormat = "mp2";
}
}
if (audioFormat.ContainsIgnoreCase("Opus"))
{
m.AudioFormat = "opus";
return;
}
if (audioFormat.ContainsIgnoreCase("PCM"))
{
m.AudioFormat = "pcm_s16le";
return;
}
if (audioFormat.ContainsIgnoreCase("ADPCM"))
{
m.AudioFormat = "pcm_s16le";
return;
}
if (audioFormat.ContainsIgnoreCase("Vorbis"))
{
m.AudioFormat = "vorbis";
return;
}
if (audioFormat.ContainsIgnoreCase("WMA"))
{
m.AudioFormat = "wmav1";
return;
}
}
private void MigrateAudioCodecLegacy(MediaInfo198 mediaInfo, MediaInfo199 m)
{
var audioFormat = mediaInfo.AudioFormat;
m.AudioFormat = audioFormat;
if (audioFormat.IsNullOrWhiteSpace())
{
m.AudioFormat = null;
return;
}
if (audioFormat.EqualsIgnoreCase("AC-3"))
{
m.AudioFormat = "ac3";
return;
}
if (audioFormat.EqualsIgnoreCase("E-AC-3"))
{
m.AudioFormat = "eac3";
return;
}
if (audioFormat.EqualsIgnoreCase("AAC"))
{
m.AudioFormat = "aac";
return;
}
if (audioFormat.EqualsIgnoreCase("MPEG Audio") && mediaInfo.AudioProfile == "Layer 3")
{
m.AudioFormat = "mp3";
return;
}
if (audioFormat.EqualsIgnoreCase("DTS"))
{
m.AudioFormat = "DTS";
return;
}
if (audioFormat.EqualsIgnoreCase("TrueHD"))
{
m.AudioFormat = "truehd";
return;
}
if (audioFormat.EqualsIgnoreCase("FLAC"))
{
m.AudioFormat = "flac";
return;
}
if (audioFormat.EqualsIgnoreCase("Vorbis"))
{
m.AudioFormat = "vorbis";
return;
}
if (audioFormat.EqualsIgnoreCase("Opus"))
{
m.AudioFormat = "opus";
return;
}
}
private void MigrateAudioChannelPositions(MediaInfo198 mediaInfo, MediaInfo199 m)
{
var audioChannels = FormatAudioChannelsFromAudioChannelPositions(mediaInfo);
if (audioChannels == null || audioChannels == 0.0m)
{
audioChannels = FormatAudioChannelsFromAudioChannelPositionsText(mediaInfo);
}
if (audioChannels == null || audioChannels == 0.0m)
{
audioChannels = FormatAudioChannelsFromAudioChannels(mediaInfo);
}
audioChannels ??= 0;
m.AudioChannelPositions = audioChannels.ToString();
}
private decimal? FormatAudioChannelsFromAudioChannelPositions(MediaInfo198 mediaInfo)
{
var audioChannelPositions = mediaInfo.AudioChannelPositions;
if (audioChannelPositions.IsNullOrWhiteSpace())
{
return null;
}
try
{
if (audioChannelPositions.Contains("+"))
{
return audioChannelPositions.Split('+')
.Sum(s => decimal.Parse(s.Trim(), CultureInfo.InvariantCulture));
}
if (audioChannelPositions.Contains("/"))
{
var channelStringList = Regex.Replace(audioChannelPositions,
@"^\d+\sobjects",
"",
RegexOptions.Compiled | RegexOptions.IgnoreCase)
.Replace("Object Based / ", "")
.Split(new string[] { " / " }, StringSplitOptions.RemoveEmptyEntries)
.FirstOrDefault()
?.Split('/');
var positions = default(decimal);
if (channelStringList == null)
{
return 0;
}
foreach (var channel in channelStringList)
{
var channelSplit = channel.Split(new string[] { "." }, StringSplitOptions.None);
if (channelSplit.Length == 3)
{
positions += decimal.Parse(string.Format("{0}.{1}", channelSplit[1], channelSplit[2]), CultureInfo.InvariantCulture);
}
else
{
positions += decimal.Parse(channel, CultureInfo.InvariantCulture);
}
}
return positions;
}
}
catch
{
}
return null;
}
private decimal? FormatAudioChannelsFromAudioChannelPositionsText(MediaInfo198 mediaInfo)
{
var audioChannelPositionsTextContainer = mediaInfo.AudioChannelPositionsTextContainer;
var audioChannelPositionsTextStream = mediaInfo.AudioChannelPositionsTextStream;
var audioChannelsContainer = mediaInfo.AudioChannelsContainer;
var audioChannelsStream = mediaInfo.AudioChannelsStream;
//Skip if the positions texts give us nothing
if ((audioChannelPositionsTextContainer.IsNullOrWhiteSpace() || audioChannelPositionsTextContainer == "Object Based") &&
(audioChannelPositionsTextStream.IsNullOrWhiteSpace() || audioChannelPositionsTextStream == "Object Based"))
{
return null;
}
try
{
if (audioChannelsStream > 0)
{
return audioChannelPositionsTextStream.ContainsIgnoreCase("LFE") ? audioChannelsStream - 1 + 0.1m : audioChannelsStream;
}
return audioChannelPositionsTextContainer.ContainsIgnoreCase("LFE") ? audioChannelsContainer - 1 + 0.1m : audioChannelsContainer;
}
catch
{
}
return null;
}
private decimal? FormatAudioChannelsFromAudioChannels(MediaInfo198 mediaInfo)
{
var audioChannelsContainer = mediaInfo.AudioChannelsContainer;
var audioChannelsStream = mediaInfo.AudioChannelsStream;
var audioFormat = (mediaInfo.AudioFormat ?? string.Empty).Trim().Split(new[] { " / " }, StringSplitOptions.RemoveEmptyEntries);
var splitAdditionalFeatures = (mediaInfo.AudioAdditionalFeatures ?? string.Empty).Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
// Workaround https://github.com/MediaArea/MediaInfo/issues/299 for DTS-X Audio
if (audioFormat.ContainsIgnoreCase("DTS") &&
splitAdditionalFeatures.ContainsIgnoreCase("XLL") &&
splitAdditionalFeatures.ContainsIgnoreCase("X") &&
audioChannelsContainer > 0)
{
return audioChannelsContainer - 1 + 0.1m;
}
// FLAC 6 channels is likely 5.1
if (audioFormat.ContainsIgnoreCase("FLAC") && audioChannelsContainer == 6)
{
return 5.1m;
}
if (mediaInfo.SchemaRevision > 5)
{
return audioChannelsStream > 0 ? audioChannelsStream : audioChannelsContainer;
}
if (mediaInfo.SchemaRevision >= 3)
{
return audioChannelsContainer;
}
return null;
}
private List<string> MigrateLanguages(string mediaInfoLanguages)
{
var languages = new List<string>();
var tokens = mediaInfoLanguages.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList();
var cultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures);
for (int i = 0; i < tokens.Count; i++)
{
if (tokens[i] == "Swedis")
{
// Probably typo in mediainfo (should be 'Swedish')
languages.Add("swe");
continue;
}
if (tokens[i] == "Chinese" && OsInfo.IsNotWindows)
{
// Mono only has 'Chinese (Simplified)' & 'Chinese (Traditional)'
languages.Add("zho");
continue;
}
if (tokens[i] == "Norwegian")
{
languages.Add("nor");
continue;
}
try
{
var cultureInfo = cultures.FirstOrDefault(p => p.EnglishName.RemoveAccent() == tokens[i]);
if (cultureInfo != null)
{
languages.Add(cultureInfo.ThreeLetterISOLanguageName.ToLowerInvariant());
}
}
catch
{
}
}
return languages;
}
private string MigratePrimaries(string primary)
{
return primary.IsNotNullOrWhiteSpace() ? primary.Replace("BT.", "bt") : primary;
}
private string MigrateTransferCharacteristics(string transferCharacteristics)
{
if (transferCharacteristics == "PQ")
{
return "smpte2084";
}
if (transferCharacteristics == "HLG")
{
return "arib-std-b67";
}
return "bt709";
}
private static string GetSceneNameMatch(string sceneName, params string[] tokens)
{
sceneName = sceneName.IsNotNullOrWhiteSpace() ? Parser.Parser.RemoveFileExtension(sceneName) : string.Empty;
foreach (var token in tokens)
{
if (sceneName.ContainsIgnoreCase(token))
{
return token;
}
}
// Last token is the default.
return tokens.Last();
}
public class MediaInfoRaw : ModelBase
{
public string MediaInfo { get; set; }
public string SceneName { get; set; }
}
public class MediaInfoBase
{
public int SchemaRevision { get; set; }
}
public class MediaInfo198 : MediaInfoBase
{
public string ContainerFormat { get; set; }
// Deprecated according to MediaInfo
public string VideoCodec { get; set; }
public string VideoFormat { get; set; }
public string VideoCodecID { get; set; }
public string VideoProfile { get; set; }
public string VideoCodecLibrary { get; set; }
public int VideoBitrate { get; set; }
public int VideoBitDepth { get; set; }
public int VideoMultiViewCount { get; set; }
public string VideoColourPrimaries { get; set; }
public string VideoTransferCharacteristics { get; set; }
public string VideoHdrFormat { get; set; }
public string VideoHdrFormatCompatibility { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public string AudioFormat { get; set; }
public string AudioCodecID { get; set; }
public string AudioCodecLibrary { get; set; }
public string AudioAdditionalFeatures { get; set; }
public int AudioBitrate { get; set; }
public TimeSpan RunTime { get; set; }
public int AudioStreamCount { get; set; }
public int AudioChannelsContainer { get; set; }
public int AudioChannelsStream { get; set; }
public string AudioChannelPositions { get; set; }
public string AudioChannelPositionsTextContainer { get; set; }
public string AudioChannelPositionsTextStream { get; set; }
public string AudioProfile { get; set; }
public decimal VideoFps { get; set; }
public string AudioLanguages { get; set; }
public string Subtitles { get; set; }
public string ScanType { get; set; }
}
public class MediaInfo199 : MediaInfoBase
{
public string ContainerFormat { get; set; }
public string VideoFormat { get; set; }
public string VideoCodecID { get; set; }
public string VideoProfile { get; set; }
public int VideoBitrate { get; set; }
public int VideoBitDepth { get; set; }
public int VideoMultiViewCount { get; set; }
public string VideoColourPrimaries { get; set; }
public string VideoTransferCharacteristics { get; set; }
public HdrFormat VideoHdrFormat { get; set; }
public int Height { get; set; }
public int Width { get; set; }
public string AudioFormat { get; set; }
public string AudioCodecID { get; set; }
public string AudioProfile { get; set; }
public int AudioBitrate { get; set; }
public TimeSpan RunTime { get; set; }
public int AudioStreamCount { get; set; }
public int AudioChannels { get; set; }
public string AudioChannelPositions { get; set; }
public decimal VideoFps { get; set; }
public List<string> AudioLanguages { get; set; }
public List<string> Subtitles { get; set; }
public string ScanType { get; set; }
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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();

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
@@ -20,9 +20,8 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
ILogger<NzbDroneSQLiteProcessor> logger,
IOptionsSnapshot<ProcessorOptions> options,
IConnectionStringAccessor connectionStringAccessor,
IServiceProvider serviceProvider,
SQLiteQuoter quoter)
: base(factory, generator, logger, options, connectionStringAccessor, serviceProvider, quoter)
IServiceProvider serviceProvider)
: base(factory, generator, logger, options, connectionStringAccessor, serviceProvider)
{
}

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