mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-03-15 15:54:35 -04:00
Compare commits
127 Commits
v2.0.0.352
...
stats
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a19776553e | ||
|
|
c20e0667d4 | ||
|
|
4771b1c1b2 | ||
|
|
8e07a39ec0 | ||
|
|
9895b5bc1d | ||
|
|
e89a1bc0fb | ||
|
|
9b0654c7f2 | ||
|
|
56da824e98 | ||
|
|
f84b7904e6 | ||
|
|
9f523bb167 | ||
|
|
f38d5de946 | ||
|
|
de379b2e47 | ||
|
|
64e90f35c8 | ||
|
|
bbfe8c27c6 | ||
|
|
2f50074123 | ||
|
|
5cfaed7b26 | ||
|
|
0d19f645e8 | ||
|
|
45d4371328 | ||
|
|
889933cb41 | ||
|
|
958b294152 | ||
|
|
cf25097cd1 | ||
|
|
902e0dd5d6 | ||
|
|
d943551a7f | ||
|
|
46304b8a71 | ||
|
|
6e2fc186ca | ||
|
|
845689401d | ||
|
|
4fb9cc5e8d | ||
|
|
487581a01a | ||
|
|
2bc771d91e | ||
|
|
8bd7969328 | ||
|
|
5876ab487c | ||
|
|
9c14ca0f39 | ||
|
|
81ca352b2f | ||
|
|
0edfed5b95 | ||
|
|
0ff053415c | ||
|
|
a0ee607ae6 | ||
|
|
2723e2a7b8 | ||
|
|
6e105ce2c6 | ||
|
|
9fc7fceda4 | ||
|
|
2b72c0e328 | ||
|
|
536aa350f0 | ||
|
|
7c382c0e0c | ||
|
|
dda0d3259f | ||
|
|
a96718f7b3 | ||
|
|
7ca67fe57a | ||
|
|
8373024f9d | ||
|
|
b62ef0c40c | ||
|
|
376481eda5 | ||
|
|
800fa42982 | ||
|
|
4e728c3a02 | ||
|
|
663d254ced | ||
|
|
8753c232c7 | ||
|
|
221f3ef08c | ||
|
|
b59175a87c | ||
|
|
37c621dcdb | ||
|
|
eaf3228bb7 | ||
|
|
c8debbf470 | ||
|
|
8dcd8d17b5 | ||
|
|
cfe121c777 | ||
|
|
b4f83d8a4e | ||
|
|
9039d7e694 | ||
|
|
7a25717da6 | ||
|
|
7e1c444c02 | ||
|
|
dc3f7c9bda | ||
|
|
de754169fb | ||
|
|
2d3c3bbb0c | ||
|
|
15cefe4a43 | ||
|
|
95da301975 | ||
|
|
e03906b294 | ||
|
|
7921dd3f96 | ||
|
|
9f066f7a6b | ||
|
|
81d131e732 | ||
|
|
0552b56b71 | ||
|
|
3952ee402b | ||
|
|
0b3e27cb44 | ||
|
|
4fa4b3507e | ||
|
|
8c211364e2 | ||
|
|
2d9917d074 | ||
|
|
d514699ab7 | ||
|
|
dc176a83b3 | ||
|
|
69e3516a89 | ||
|
|
c8a0f9fa7a | ||
|
|
c2b9504b15 | ||
|
|
2693a3df2e | ||
|
|
8062466ab8 | ||
|
|
6cde1dd5ae | ||
|
|
b6c4a97675 | ||
|
|
a9444cef30 | ||
|
|
bf217a7093 | ||
|
|
b6b5355261 | ||
|
|
bc37084ec4 | ||
|
|
0a1a30f2af | ||
|
|
7e023a7944 | ||
|
|
91f68de8a7 | ||
|
|
994e2a6c57 | ||
|
|
04da2d845a | ||
|
|
d3b87bc3e8 | ||
|
|
554c81f251 | ||
|
|
6de3f9dd0b | ||
|
|
e9692d5b9c | ||
|
|
1a74990e9b | ||
|
|
3b9ac8699d | ||
|
|
ea6ae85f7a | ||
|
|
b02b9f026f | ||
|
|
c419e7b710 | ||
|
|
cd9132520d | ||
|
|
08d19df3f7 | ||
|
|
b34879b4f6 | ||
|
|
6b9c4af591 | ||
|
|
c00c207517 | ||
|
|
6f7fea3591 | ||
|
|
83eebfe153 | ||
|
|
fce3f86be7 | ||
|
|
2d42c59d70 | ||
|
|
f0933b9786 | ||
|
|
e4e687c2a4 | ||
|
|
44de353b8b | ||
|
|
aac4938598 | ||
|
|
d37b24cd0b | ||
|
|
c9a36fe4b2 | ||
|
|
f01a21ce43 | ||
|
|
cc72699b8a | ||
|
|
04de0049fe | ||
|
|
330554edb0 | ||
|
|
a06a3fa5d6 | ||
|
|
e8d6d62fba | ||
|
|
9cc8ed545f |
1
.idea/jsLibraryMappings.xml
generated
1
.idea/jsLibraryMappings.xml
generated
@@ -2,5 +2,6 @@
|
||||
<project version="4">
|
||||
<component name="JavaScriptLibraryMappings">
|
||||
<file url="file://$PROJECT_DIR$" libraries="{Sonarr node_modules}" />
|
||||
<includedPredefinedLibrary name="ECMAScript 6" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -16,7 +16,7 @@ Setup guides, FAQ, the more information we have on the wiki the better.
|
||||
### Getting started ###
|
||||
|
||||
1. Fork Sonarr
|
||||
2. Clone (develop branch)
|
||||
2. Clone (develop branch) *you may need pull in submodules separately if you client doesn't clone them automatically (CurlSharp)*
|
||||
3. Run `npm install`
|
||||
4. Run `gulp watch` - Used to compile the UI components and copy them (leave this window open)
|
||||
5. Compile in Visual Studio
|
||||
|
||||
264
build.ps1
264
build.ps1
@@ -1,263 +1 @@
|
||||
$msBuild = 'C:\Windows\Microsoft.NET\Framework64\v4.0.30319\msbuild.exe'
|
||||
$outputFolder = '.\_output'
|
||||
$outputFolderMono = '.\_output_mono'
|
||||
$outputFolderOsx = '.\_output_osx'
|
||||
$outputFolderOsxApp = '.\_output_osx_app'
|
||||
$testPackageFolder = '.\_tests\'
|
||||
$testSearchPattern = '*.Test\bin\x86\Release'
|
||||
$sourceFolder = '.\src'
|
||||
$updateFolder = $outputFolder + '\NzbDrone.Update'
|
||||
$updateFolderMono = $outputFolderMono + '\NzbDrone.Update'
|
||||
|
||||
Function Build()
|
||||
{
|
||||
Write-Host "##teamcity[progressStart 'Build']"
|
||||
|
||||
$clean = $msbuild + " src\nzbdrone.sln /t:Clean /m"
|
||||
$build = $msbuild + " src\nzbdrone.sln /p:Configuration=Release /p:Platform=x86 /t:Build /m"
|
||||
|
||||
if(Test-Path $outputFolder)
|
||||
{
|
||||
Remove-Item -Recurse -Force $outputFolder -ErrorAction Continue
|
||||
}
|
||||
|
||||
Invoke-Expression $clean
|
||||
CheckExitCode
|
||||
|
||||
Invoke-Expression $build
|
||||
CheckExitCode
|
||||
|
||||
CleanFolder $outputFolder
|
||||
|
||||
AddJsonNet
|
||||
|
||||
Write-Host "Removing Mono.Posix.dll"
|
||||
Remove-Item "$outputFolder\Mono.Posix.dll"
|
||||
|
||||
Write-Host "##teamcity[progressFinish 'Build']"
|
||||
}
|
||||
|
||||
Function CleanFolder($path, $keepConfigFiles)
|
||||
{
|
||||
Write-Host Removing XMLDoc files
|
||||
get-childitem $path -File -Filter *.xml -Recurse | foreach ($_) {
|
||||
|
||||
$filename = $_.FullName
|
||||
$exeFilename = $filename -replace "xml", "exe"
|
||||
$dllFilename = $filename -replace "xml", "dll"
|
||||
|
||||
if (Test-Path $exeFilename) {
|
||||
remove-item $_.fullname
|
||||
}
|
||||
|
||||
if (Test-Path $dllFilename) {
|
||||
remove-item $_.fullname
|
||||
}
|
||||
}
|
||||
|
||||
get-childitem $path -File -Filter *.transform -Recurse | foreach ($_) {remove-item $_.fullname}
|
||||
|
||||
if($keepConfigFiles -ne $true)
|
||||
{
|
||||
get-childitem $path -File -Filter *.dll.config -Recurse | foreach ($_) {remove-item $_.fullname}
|
||||
}
|
||||
|
||||
Write-Host Removing FluentValidation.Resources files
|
||||
get-childitem $path -File -Filter FluentValidation.resources.dll -recurse | foreach ($_) {remove-item $_.fullname}
|
||||
|
||||
get-childitem $path -File -Filter app.config -Recurse | foreach ($_) {remove-item $_.fullname}
|
||||
|
||||
Write-Host Removing .less files
|
||||
get-childitem $path -File -Filter *.less -Recurse | foreach ($_) {remove-item $_.fullname}
|
||||
|
||||
Write-Host Removing vshost files
|
||||
get-childitem $path -File -Filter *.vshost.exe -Recurse | foreach ($_) {remove-item $_.fullname}
|
||||
|
||||
if(Test-Path $$path\NuGet)
|
||||
{
|
||||
Write-Host Removing NuGet
|
||||
Remove-Item -Recurse -Force "$path\NuGet"
|
||||
}
|
||||
|
||||
Write-Host Removing Empty folders
|
||||
while (Get-ChildItem $path -recurse | where {!@(Get-ChildItem -force $_.fullname)} | Test-Path)
|
||||
{
|
||||
Get-ChildItem $path -Directory -recurse | where {!@(Get-ChildItem -force $_.fullname)} | Remove-Item
|
||||
}
|
||||
}
|
||||
|
||||
Function PackageMono()
|
||||
{
|
||||
Write-Host "##teamcity[progressStart 'Creating Mono Package']"
|
||||
|
||||
if(Test-Path $outputFolderMono)
|
||||
{
|
||||
Remove-Item -Recurse -Force $outputFolderMono -ErrorAction Continue
|
||||
}
|
||||
|
||||
Copy-Item $outputFolder $outputFolderMono -recurse
|
||||
|
||||
Write-Host Creating MDBs
|
||||
get-childitem $outputFolderMono -File -Include @("*.exe", "*.dll") -Exclude @("MediaInfo.dll", "sqlite3.dll") -Recurse | foreach ($_) {
|
||||
Write-Host "Creating .mdb for $_"
|
||||
& "tools\pdb2mdb\pdb2mdb.exe" $_.fullname
|
||||
}
|
||||
|
||||
Write-Host Removing PDBs
|
||||
get-childitem $outputFolderMono -File -Filter *.pdb -Recurse | foreach ($_) {remove-item $_.fullname}
|
||||
|
||||
Write-Host Removing Service helpers
|
||||
get-childitem $outputFolderMono -File -Filter ServiceUninstall.* -Recurse | foreach ($_) {remove-item $_.fullname}
|
||||
get-childitem $outputFolderMono -File -Filter ServiceInstall.* -Recurse | foreach ($_) {remove-item $_.fullname}
|
||||
|
||||
Write-Host Removing native windows binaries Sqlite, MediaInfo
|
||||
get-childitem $outputFolderMono -File -Filter sqlite3.* -Recurse | foreach ($_) {remove-item $_.fullname}
|
||||
get-childitem $outputFolderMono -File -Filter MediaInfo.* -Recurse | foreach ($_) {remove-item $_.fullname}
|
||||
|
||||
Write-Host "Adding NzbDrone.Core.dll.config (for dllmap)"
|
||||
Copy-Item "$sourceFolder\NzbDrone.Core\NzbDrone.Core.dll.config" $outputFolderMono
|
||||
|
||||
Write-Host "Adding CurlSharp.dll.config (for dllmap)"
|
||||
Copy-Item "$sourceFolder\NzbDrone.Common\CurlSharp.dll.config" $outputFolderMono
|
||||
|
||||
Write-Host Renaming NzbDrone.Console.exe to NzbDrone.exe
|
||||
Get-ChildItem $outputFolderMono -File -Filter "NzbDrone.exe*" -Recurse | foreach ($_) {remove-item $_.fullname}
|
||||
|
||||
Write-Host Removing NzbDrone.Windows
|
||||
get-childitem $outputFolderMono -File -Filter NzbDrone.Windows.* -Recurse | foreach ($_) {remove-item $_.fullname}
|
||||
|
||||
Get-ChildItem $outputFolderMono -File -Filter "NzbDrone.Console.exe*" -Recurse | foreach ($_) {
|
||||
$newName = $_.fullname -Replace ".Console",""
|
||||
|
||||
Rename-Item $_.fullname $newName
|
||||
}
|
||||
|
||||
Write-Host Adding NzbDrone.Mono to UpdatePackage
|
||||
Copy-Item $outputFolderMono\* $updateFolderMono -Filter NzbDrone.Mono.*
|
||||
|
||||
Write-Host "##teamcity[progressFinish 'Creating Mono Package']"
|
||||
}
|
||||
|
||||
Function PackageOsx()
|
||||
{
|
||||
Write-Host "##teamcity[progressStart 'Creating OS X Package']"
|
||||
|
||||
if(Test-Path $outputFolderOsx)
|
||||
{
|
||||
Remove-Item -Recurse -Force $outputFolderOsx -ErrorAction Continue
|
||||
}
|
||||
|
||||
Copy-Item $outputFolderMono $outputFolderOsx -recurse
|
||||
|
||||
Write-Host "Adding sqlite dylibs"
|
||||
Copy-Item "$sourceFolder\Libraries\sqlite\*.dylib" "$outputFolderOsx"
|
||||
|
||||
Write-Host "Adding MediaInfo dylib"
|
||||
Copy-Item "$sourceFolder\Libraries\MediaInfo\*.dylib" "$outputFolderOsx"
|
||||
|
||||
Write-Host "Adding Startup script"
|
||||
Copy-Item .\osx\Sonarr "$outputFolderOsx"
|
||||
|
||||
Write-Host "##teamcity[progressFinish 'Creating OS X Package']"
|
||||
}
|
||||
|
||||
|
||||
Function PackageOsxApp()
|
||||
{
|
||||
Write-Host "##teamcity[progressStart 'Creating OS X App Package']"
|
||||
|
||||
if(Test-Path $outputFolderOsxApp)
|
||||
{
|
||||
Remove-Item -Recurse -Force $outputFolderOsxApp -ErrorAction Continue
|
||||
}
|
||||
|
||||
Copy-Item .\osx\Sonarr.app $outputFolderOsxApp\Sonarr.app -recurse
|
||||
Copy-Item $outputFolderOsx $outputFolderOsxApp\Sonarr.app\Contents\MacOS -recurse
|
||||
|
||||
Write-Host "##teamcity[progressFinish 'Creating OS X App Package']"
|
||||
}
|
||||
|
||||
Function AddJsonNet()
|
||||
{
|
||||
get-childitem $outputFolder -File -Filter Newtonsoft.Json.* -Recurse | foreach ($_) {remove-item $_.fullname}
|
||||
Copy-Item .\src\packages\Newtonsoft.Json.*.*\lib\net35\*.dll -Destination $outputFolder
|
||||
Copy-Item .\src\packages\Newtonsoft.Json.*.*\lib\net35\*.dll -Destination $outputFolder\NzbDrone.Update
|
||||
}
|
||||
|
||||
Function PackageTests()
|
||||
{
|
||||
|
||||
Write-Host Packaging Tests
|
||||
Write-Host "##teamcity[progressStart 'Creating Test Package']"
|
||||
|
||||
if(Test-Path $testPackageFolder)
|
||||
{
|
||||
Remove-Item -Recurse -Force $testPackageFolder -ErrorAction Continue
|
||||
}
|
||||
|
||||
Get-ChildItem -Recurse -Directory | Where-Object {$_.FullName -like $testSearchPattern} | foreach($_){
|
||||
Copy-Item -Recurse ($_.FullName + "\*") $testPackageFolder -ErrorAction Ignore
|
||||
}
|
||||
|
||||
.\src\.nuget\NuGet.exe install NUnit.Runners -Version 2.6.1 -Output $testPackageFolder
|
||||
|
||||
Copy-Item $outputFolder\*.dll -Destination $testPackageFolder -Force
|
||||
Copy-Item $outputFolder\*.pdb -Destination $testPackageFolder -Force
|
||||
Copy-Item .\*.sh -Destination $testPackageFolder -Force
|
||||
|
||||
Write-Host Creating MDBs for tests
|
||||
get-childitem $testPackageFolder -File -Include @("*.exe", "*.dll") -Exclude @("MediaInfo.dll", "sqlite3.dll") -Recurse | foreach ($_) {
|
||||
Write-Host "Creating .mdb for $_"
|
||||
& "tools\pdb2mdb\pdb2mdb.exe" $_.fullname
|
||||
}
|
||||
|
||||
get-childitem $testPackageFolder -File -Filter *log.config | foreach ($_) {remove-item $_.fullname}
|
||||
|
||||
CleanFolder $testPackageFolder $true
|
||||
|
||||
Write-Host "Adding NzbDrone.Core.dll.config (for dllmap)"
|
||||
Copy-Item "$sourceFolder\NzbDrone.Core\NzbDrone.Core.dll.config" -Destination $testPackageFolder -Force
|
||||
|
||||
Write-Host "Copying CurlSharp libraries"
|
||||
Copy-Item $sourceFolder\ExternalModules\CurlSharp\libs\i386\* $testPackageFolder
|
||||
|
||||
Write-Host "##teamcity[progressFinish 'Creating Test Package']"
|
||||
}
|
||||
|
||||
Function RunGulp()
|
||||
{
|
||||
Write-Host "##teamcity[progressStart 'Running Gulp']"
|
||||
Invoke-Expression 'npm install'
|
||||
CheckExitCode
|
||||
|
||||
Invoke-Expression 'gulp build' -ErrorAction Continue -Verbose
|
||||
CheckExitCode
|
||||
|
||||
Write-Host "##teamcity[progressFinish 'Running Gulp']"
|
||||
}
|
||||
|
||||
Function CheckExitCode()
|
||||
{
|
||||
if ($lastexitcode -ne 0)
|
||||
{
|
||||
Write-Host $errorMessage
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
Function CleanupWindowsPackage()
|
||||
{
|
||||
Write-Host Removing NzbDrone.Mono
|
||||
get-childitem $outputFolder -File -Filter NzbDrone.Mono.* -Recurse | foreach ($_) {remove-item $_.fullname}
|
||||
|
||||
Write-Host Adding NzbDrone.Windows to UpdatePackage
|
||||
Copy-Item $outputFolder\* $updateFolder -Filter NzbDrone.Windows.*
|
||||
}
|
||||
|
||||
Build
|
||||
RunGulp
|
||||
PackageMono
|
||||
PackageOsx
|
||||
PackageOsxApp
|
||||
PackageTests
|
||||
CleanupWindowsPackage
|
||||
Write-Warning "DEPRECATED -- Please use build.sh instead."
|
||||
59
build.sh
59
build.sh
@@ -5,11 +5,13 @@ outputFolderMono='./_output_mono'
|
||||
outputFolderOsx='./_output_osx'
|
||||
outputFolderOsxApp='./_output_osx_app'
|
||||
testPackageFolder='./_tests/'
|
||||
testSearchPattern='*.Test/bin/x86/Release/*'
|
||||
testSearchPattern='*.Test/bin/x86/Release'
|
||||
sourceFolder='./src'
|
||||
slnFile=$sourceFolder/NzbDrone.sln
|
||||
updateFolder=$outputFolder/NzbDrone.Update
|
||||
updateFolderMono=$outputFolderMono/NzbDrone.Update
|
||||
|
||||
nuget='tools/nuget/nuget.exe';
|
||||
CheckExitCode()
|
||||
{
|
||||
"$@"
|
||||
@@ -26,14 +28,6 @@ CleanFolder()
|
||||
local path=$1
|
||||
local keepConfigFiles=$2
|
||||
|
||||
echo "Removing XMLDoc files"
|
||||
local xmlfiles=( $(find $path -name "*.xml") )
|
||||
for filename in "${xmlfiles[@]}"
|
||||
do
|
||||
if [ -e ${filename%.xml}.dll ] || [ -e ${filename%.xml}.exe ] ; then
|
||||
rm $filename
|
||||
fi
|
||||
done
|
||||
|
||||
find $path -name "*.transform" -exec rm "{}" \;
|
||||
|
||||
@@ -51,10 +45,8 @@ CleanFolder()
|
||||
echo "Removing vshost files"
|
||||
find $path -name "*.vshost.exe" -exec rm "{}" \;
|
||||
|
||||
if [ -d $path/NuGet ] ; then
|
||||
echo "Removing NuGet"
|
||||
rm -rf $path/NuGet
|
||||
fi
|
||||
echo "Removing dylib files"
|
||||
find $path -name "*.dylib" -exec rm "{}" \;
|
||||
|
||||
echo "Removing Empty folders"
|
||||
find $path -depth -empty -type d -exec rm -r "{}" \;
|
||||
@@ -72,15 +64,17 @@ AddJsonNet()
|
||||
BuildWithMSBuild()
|
||||
{
|
||||
export PATH=$msBuild:$PATH
|
||||
CheckExitCode MSBuild.exe $sourceFolder/NzbDrone.sln //t:Clean //m
|
||||
CheckExitCode MSBuild.exe $sourceFolder/NzbDrone.sln //p:Configuration=Release //p:Platform=x86 //t:Build //m
|
||||
CheckExitCode MSBuild.exe $slnFile //t:Clean //m
|
||||
$nuget restore $slnFile
|
||||
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x86 //t:Build //m //p:AllowedReferenceRelatedFileExtensions=.pdb
|
||||
}
|
||||
|
||||
BuildWithXbuild()
|
||||
{
|
||||
export MONO_IOMAP=case
|
||||
CheckExitCode xbuild /t:Clean $sourceFolder/NzbDrone.sln
|
||||
CheckExitCode xbuild /p:Configuration=Release /p:Platform=x86 /t:Build $sourceFolder/NzbDrone.sln
|
||||
CheckExitCode xbuild /t:Clean $slnFile
|
||||
mono $nuget restore $slnFile
|
||||
CheckExitCode xbuild /p:Configuration=Release /p:Platform=x86 /t:Build /p:AllowedReferenceRelatedFileExtensions=.pdb $slnFile
|
||||
}
|
||||
|
||||
Build()
|
||||
@@ -94,9 +88,9 @@ Build()
|
||||
else
|
||||
BuildWithXbuild
|
||||
fi
|
||||
|
||||
|
||||
CleanFolder $outputFolder false
|
||||
|
||||
|
||||
AddJsonNet
|
||||
|
||||
echo "Removing Mono.Posix.dll"
|
||||
@@ -107,11 +101,12 @@ Build()
|
||||
|
||||
RunGulp()
|
||||
{
|
||||
echo "##teamcity[progressStart 'Running Gulp']"
|
||||
|
||||
echo "##teamcity[progressStart 'npm install']"
|
||||
CheckExitCode npm install
|
||||
CheckExitCode gulp build
|
||||
echo "##teamcity[progressFinish 'npm install']"
|
||||
|
||||
echo "##teamcity[progressStart 'Running Gulp']"
|
||||
CheckExitCode gulp build
|
||||
echo "##teamcity[progressFinish 'Running Gulp']"
|
||||
}
|
||||
|
||||
@@ -119,7 +114,16 @@ CreateMdbs()
|
||||
{
|
||||
local path=$1
|
||||
if [ $runtime = "dotnet" ] ; then
|
||||
find $path \( -name "*.exe" -o -name "*.dll" \) -not -name "MediaInfo.dll" -not -name "sqlite3.dll" -exec tools/pdb2mdb/pdb2mdb.exe "{}" \;
|
||||
local pdbFiles=( $(find $path -name "*.pdb") )
|
||||
for filename in "${pdbFiles[@]}"
|
||||
do
|
||||
if [ -e ${filename%.pdb}.dll ] ; then
|
||||
tools/pdb2mdb/pdb2mdb.exe ${filename%.pdb}.dll
|
||||
fi
|
||||
if [ -e ${filename%.pdb}.exe ] ; then
|
||||
tools/pdb2mdb/pdb2mdb.exe ${filename%.pdb}.exe
|
||||
fi
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -160,6 +164,8 @@ PackageMono()
|
||||
|
||||
echo "Adding NzbDrone.Mono to UpdatePackage"
|
||||
cp $outputFolderMono/NzbDrone.Mono.* $updateFolderMono
|
||||
|
||||
echo "##teamcity[progressFinish 'Creating Mono Package']"
|
||||
}
|
||||
|
||||
PackageOsx()
|
||||
@@ -199,13 +205,12 @@ PackageTests()
|
||||
rm -rf $testPackageFolder
|
||||
mkdir $testPackageFolder
|
||||
|
||||
find . -maxdepth 6 -path $testSearchPattern -exec cp -r "{}" $testPackageFolder \;
|
||||
find $sourceFolder -path $testSearchPattern -exec cp -r -u -T "{}" $testPackageFolder \;
|
||||
|
||||
if [ $runtime = "dotnet" ] ; then
|
||||
$sourceFolder/.nuget/NuGet.exe install NUnit.Runners -Version 2.6.1 -Output $testPackageFolder
|
||||
cp $outputFolder/*.pdb $testPackageFolder
|
||||
$nuget install NUnit.Runners -Version 2.6.1 -Output $testPackageFolder
|
||||
else
|
||||
mono $sourceFolder/.nuget/NuGet.exe install NUnit.Runners -Version 2.6.1 -Output $testPackageFolder
|
||||
mono $nuget install NUnit.Runners -Version 2.6.1 -Output $testPackageFolder
|
||||
fi
|
||||
|
||||
cp $outputFolder/*.dll $testPackageFolder
|
||||
|
||||
2
debian/copyright
vendored
2
debian/copyright
vendored
@@ -3,7 +3,7 @@ Upstream-Name: nzbdrone
|
||||
Source: https://github.com/Sonarr/Sonarr
|
||||
|
||||
Files: *
|
||||
Copyright: 2010-2014 Sonarr <hello@sonarr.tv>
|
||||
Copyright: 2010-2016 Sonarr <hello@sonarr.tv>
|
||||
|
||||
License: GPL-3.0+
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ var autoprefixer = require('autoprefixer-core');
|
||||
var livereload = require('gulp-livereload');
|
||||
|
||||
var print = require('gulp-print');
|
||||
var phantom = require('./phantom');
|
||||
var paths = require('./paths');
|
||||
var errorHandler = require('./errorHandler');
|
||||
|
||||
@@ -29,14 +28,6 @@ gulp.task('less', function() {
|
||||
paths.src.root + 'System/Info/info.less'
|
||||
];
|
||||
|
||||
if (phantom) {
|
||||
src = [
|
||||
paths.src.content + 'Bootstrap/bootstrap.less',
|
||||
paths.src.content + 'Vendor/vendor.less',
|
||||
paths.src.content + 'sonarr.less'
|
||||
];
|
||||
}
|
||||
|
||||
return gulp.src(src)
|
||||
.pipe(print())
|
||||
.pipe(sourcemaps.init())
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
var phantom = require('./phantom');
|
||||
|
||||
var paths = {
|
||||
src : {
|
||||
root : './src/UI/',
|
||||
@@ -20,26 +18,4 @@ var paths = {
|
||||
}
|
||||
};
|
||||
|
||||
if (phantom) {
|
||||
paths = {
|
||||
src : {
|
||||
root : './UI.Phantom/',
|
||||
templates : './UI.Phantom/**/*.hbs',
|
||||
html : './UI.Phantom/*.html',
|
||||
partials : './UI.Phantom/**/*Partial.hbs',
|
||||
scripts : './UI.Phantom/**/*.js',
|
||||
less : ['./UI.Phantom/**/*.less'],
|
||||
content : './UI.Phantom/Content/',
|
||||
images : './UI.Phantom/Content/Images/**/*',
|
||||
exclude : {
|
||||
libs : '!./UI.Phantom/JsLibraries/**'
|
||||
}
|
||||
},
|
||||
dest : {
|
||||
root : './_output/UI.Phantom/',
|
||||
content : './_output/UI.Phantom/Content/'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = paths;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
// Switch to phantom.
|
||||
// Example:
|
||||
// gulp --phantom
|
||||
|
||||
var phantom = false;
|
||||
process.argv.forEach(function(val, index, array) {
|
||||
if (val === '--phantom') {
|
||||
phantom = true;
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Phantom:', phantom);
|
||||
|
||||
module.exports = phantom;
|
||||
@@ -21,7 +21,7 @@ Sonarr is a PVR for Usenet and BitTorrent users. It can monitor multiple RSS fee
|
||||
## Configuring Development Environment: ##
|
||||
|
||||
### Requirements ###
|
||||
- Visual Studio 2013 [Free Community Edition](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx)
|
||||
- Visual Studio 2015 [Free Community Edition](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx)
|
||||
- [Git](http://git-scm.com/downloads)
|
||||
- [NodeJS](http://nodejs.org/download/)
|
||||
- [Gulp](http://gulpjs.com)
|
||||
@@ -45,7 +45,7 @@ Sonarr is a PVR for Usenet and BitTorrent users. It can monitor multiple RSS fee
|
||||
|
||||
### License ###
|
||||
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
|
||||
Copyright 2010-2015
|
||||
Copyright 2010-2016
|
||||
|
||||
|
||||
### Sponsors ###
|
||||
|
||||
17
rename.ps1
17
rename.ps1
@@ -1,17 +0,0 @@
|
||||
Param(
|
||||
[Parameter(Mandatory=$true, Position=0, HelpMessage="A branch name is #requires required")]
|
||||
[string]$branch,
|
||||
[Parameter(Mandatory=$true, Position=1, HelpMessage="A version is required")]
|
||||
[string]$version
|
||||
)
|
||||
|
||||
if ($branch -eq "<default>")
|
||||
{
|
||||
$branch = "teamcity";
|
||||
}
|
||||
|
||||
Write-Host $branch;
|
||||
Write-Host $version;
|
||||
Write-Host "NzbDrone.$branch.$version.zip";
|
||||
|
||||
Rename-Item "nzbdrone.zip" "NzbDrone.$branch.$version.zip"
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<solution>
|
||||
<add key="disableSourceControlIntegration" value="true" />
|
||||
</solution>
|
||||
</configuration>
|
||||
Binary file not shown.
@@ -1,136 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">$(MSBuildProjectDirectory)\..\</SolutionDir>
|
||||
|
||||
<!-- Enable the restore command to run before builds -->
|
||||
<RestorePackages Condition=" '$(RestorePackages)' == '' ">false</RestorePackages>
|
||||
|
||||
<!-- Property that enables building a package from a project -->
|
||||
<BuildPackage Condition=" '$(BuildPackage)' == '' ">false</BuildPackage>
|
||||
|
||||
<!-- Determines if package restore consent is required to restore packages -->
|
||||
<RequireRestoreConsent Condition=" '$(RequireRestoreConsent)' != 'false' ">true</RequireRestoreConsent>
|
||||
|
||||
<!-- Download NuGet.exe if it does not already exist -->
|
||||
<DownloadNuGetExe Condition=" '$(DownloadNuGetExe)' == '' ">false</DownloadNuGetExe>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(PackageSources)' == '' ">
|
||||
<!-- Package sources used to restore packages. By default, registered sources under %APPDATA%\NuGet\NuGet.Config will be used -->
|
||||
<!-- The official NuGet package source (https://www.nuget.org/api/v2/) will be excluded if package sources are specified and it does not appear in the list -->
|
||||
<!--
|
||||
<PackageSource Include="https://www.nuget.org/api/v2/" />
|
||||
<PackageSource Include="https://my-nuget-source/nuget/" />
|
||||
-->
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT'">
|
||||
<!-- Windows specific commands -->
|
||||
<NuGetToolsPath>$([System.IO.Path]::Combine($(SolutionDir), ".nuget"))</NuGetToolsPath>
|
||||
<PackagesConfig>$([System.IO.Path]::Combine($(ProjectDir), "packages.config"))</PackagesConfig>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(OS)' != 'Windows_NT'">
|
||||
<!-- We need to launch nuget.exe with the mono command if we're not on windows -->
|
||||
<NuGetToolsPath>$(SolutionDir).nuget</NuGetToolsPath>
|
||||
<PackagesConfig>packages.config</PackagesConfig>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- NuGet command -->
|
||||
<NuGetExePath Condition=" '$(NuGetExePath)' == '' ">$(NuGetToolsPath)\NuGet.exe</NuGetExePath>
|
||||
<PackageSources Condition=" $(PackageSources) == '' ">@(PackageSource)</PackageSources>
|
||||
|
||||
<NuGetCommand Condition=" '$(OS)' == 'Windows_NT'">"$(NuGetExePath)"</NuGetCommand>
|
||||
<NuGetCommand Condition=" '$(OS)' != 'Windows_NT' ">mono --runtime=v4.0.30319 $(NuGetExePath)</NuGetCommand>
|
||||
|
||||
<PackageOutputDir Condition="$(PackageOutputDir) == ''">$(TargetDir.Trim('\\'))</PackageOutputDir>
|
||||
|
||||
<RequireConsentSwitch Condition=" $(RequireRestoreConsent) == 'true' ">-RequireConsent</RequireConsentSwitch>
|
||||
<NonInteractiveSwitch Condition=" '$(VisualStudioVersion)' != '' AND '$(OS)' == 'Windows_NT' ">-NonInteractive</NonInteractiveSwitch>
|
||||
|
||||
<PaddedSolutionDir Condition=" '$(OS)' == 'Windows_NT'">"$(SolutionDir) "</PaddedSolutionDir>
|
||||
<PaddedSolutionDir Condition=" '$(OS)' != 'Windows_NT' ">"$(SolutionDir)"</PaddedSolutionDir>
|
||||
|
||||
<!-- Commands -->
|
||||
<RestoreCommand>$(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir)</RestoreCommand>
|
||||
<BuildCommand>$(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols</BuildCommand>
|
||||
|
||||
<!-- We need to ensure packages are restored prior to assembly resolve -->
|
||||
<BuildDependsOn Condition="$(RestorePackages) == 'true'">
|
||||
RestorePackages;
|
||||
$(BuildDependsOn);
|
||||
</BuildDependsOn>
|
||||
|
||||
<!-- Make the build depend on restore packages -->
|
||||
<BuildDependsOn Condition="$(BuildPackage) == 'true'">
|
||||
$(BuildDependsOn);
|
||||
BuildPackage;
|
||||
</BuildDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="CheckPrerequisites">
|
||||
<!-- Raise an error if we're unable to locate nuget.exe -->
|
||||
<Error Condition="'$(DownloadNuGetExe)' != 'true' AND !Exists('$(NuGetExePath)')" Text="Unable to locate '$(NuGetExePath)'" />
|
||||
<!--
|
||||
Take advantage of MsBuild's build dependency tracking to make sure that we only ever download nuget.exe once.
|
||||
This effectively acts as a lock that makes sure that the download operation will only happen once and all
|
||||
parallel builds will have to wait for it to complete.
|
||||
-->
|
||||
<MsBuild Targets="_DownloadNuGet" Projects="$(MSBuildThisFileFullPath)" Properties="Configuration=NOT_IMPORTANT;DownloadNuGetExe=$(DownloadNuGetExe)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="_DownloadNuGet">
|
||||
<DownloadNuGet OutputFilename="$(NuGetExePath)" Condition=" '$(DownloadNuGetExe)' == 'true' AND !Exists('$(NuGetExePath)')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="RestorePackages" DependsOnTargets="CheckPrerequisites">
|
||||
<Exec Command="$(RestoreCommand)"
|
||||
Condition="'$(OS)' != 'Windows_NT' And Exists('$(PackagesConfig)')" />
|
||||
|
||||
<Exec Command="$(RestoreCommand)"
|
||||
LogStandardErrorAsError="true"
|
||||
Condition="'$(OS)' == 'Windows_NT' And Exists('$(PackagesConfig)')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildPackage" DependsOnTargets="CheckPrerequisites">
|
||||
<Exec Command="$(BuildCommand)"
|
||||
Condition=" '$(OS)' != 'Windows_NT' " />
|
||||
|
||||
<Exec Command="$(BuildCommand)"
|
||||
LogStandardErrorAsError="true"
|
||||
Condition=" '$(OS)' == 'Windows_NT' " />
|
||||
</Target>
|
||||
|
||||
<UsingTask TaskName="DownloadNuGet" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
|
||||
<ParameterGroup>
|
||||
<OutputFilename ParameterType="System.String" Required="true" />
|
||||
</ParameterGroup>
|
||||
<Task>
|
||||
<Reference Include="System.Core" />
|
||||
<Using Namespace="System" />
|
||||
<Using Namespace="System.IO" />
|
||||
<Using Namespace="System.Net" />
|
||||
<Using Namespace="Microsoft.Build.Framework" />
|
||||
<Using Namespace="Microsoft.Build.Utilities" />
|
||||
<Code Type="Fragment" Language="cs">
|
||||
<![CDATA[
|
||||
try {
|
||||
OutputFilename = Path.GetFullPath(OutputFilename);
|
||||
|
||||
Log.LogMessage("Downloading latest version of NuGet.exe...");
|
||||
WebClient webClient = new WebClient();
|
||||
webClient.DownloadFile("https://www.nuget.org/nuget.exe", OutputFilename);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.LogErrorFromException(ex);
|
||||
return false;
|
||||
}
|
||||
]]>
|
||||
</Code>
|
||||
</Task>
|
||||
</UsingTask>
|
||||
</Project>
|
||||
Binary file not shown.
@@ -1,9 +0,0 @@
|
||||
<?xml version ="1.0"?>
|
||||
<!-- This allows mt.exe to run on machines with the CLR v4 installed but not 1.1 or 2.0 -->
|
||||
<configuration>
|
||||
<startup useLegacyV2RuntimeActivationPolicy="true">
|
||||
<supportedRuntime version="v4.0"/>
|
||||
<supportedRuntime version="v2.0.50727"/>
|
||||
<supportedRuntime version="v1.1.4322"/>
|
||||
</startup>
|
||||
</configuration>
|
||||
@@ -73,13 +73,6 @@
|
||||
<PostBuildEvent>
|
||||
</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
|
||||
</Target>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
|
||||
@@ -85,13 +85,6 @@
|
||||
<PostBuildEvent>
|
||||
</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
|
||||
</Target>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
|
||||
@@ -144,7 +144,6 @@
|
||||
<PreBuildEvent>
|
||||
</PreBuildEvent>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
|
||||
@@ -276,10 +276,9 @@
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
|
||||
@@ -102,7 +102,6 @@
|
||||
</ItemGroup>
|
||||
<Import Project="..\Common\Microsoft.AspNet.SignalR.targets" />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
@@ -110,4 +109,4 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -38,12 +38,12 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="FluentAssertions, Version=3.4.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.3.4.0\lib\net40\FluentAssertions.dll</HintPath>
|
||||
<Reference Include="FluentAssertions, Version=4.2.1.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.4.2.1\lib\net40\FluentAssertions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FluentAssertions.Core, Version=3.4.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.3.4.0\lib\net40\FluentAssertions.Core.dll</HintPath>
|
||||
<Reference Include="FluentAssertions.Core, Version=4.2.1.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.4.2.1\lib\net40\FluentAssertions.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="nunit.framework, Version=2.6.3.13283, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
|
||||
@@ -104,7 +104,6 @@
|
||||
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
@@ -112,4 +111,4 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="FluentAssertions" version="3.4.0" targetFramework="net40" />
|
||||
<package id="FluentAssertions" version="4.2.1" targetFramework="net40" />
|
||||
<package id="Moq" version="4.0.10827" />
|
||||
<package id="NBuilder" version="3.0.1.1" targetFramework="net40" />
|
||||
<package id="NUnit" version="2.6.3" targetFramework="net40" />
|
||||
<package id="ValueInjecter" version="2.3.3" targetFramework="net40" />
|
||||
</packages>
|
||||
</packages>
|
||||
@@ -3,6 +3,7 @@ using Nancy;
|
||||
using Nancy.Authentication.Forms;
|
||||
using Nancy.Extensions;
|
||||
using Nancy.ModelBinding;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Core.Authentication;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
@@ -23,6 +24,11 @@ namespace NzbDrone.Api.Authentication
|
||||
|
||||
private Response Login(LoginResource resource)
|
||||
{
|
||||
Ensure.That(resource.Username, () => resource.Username).IsNotNullOrWhiteSpace();
|
||||
|
||||
// TODO: A null or empty password should not be allowed, uncomment in v3
|
||||
//Ensure.That(resource.Password, () => resource.Password).IsNotNullOrWhiteSpace();
|
||||
|
||||
var user = _userService.FindUser(resource.Username, resource.Password);
|
||||
|
||||
if (user == null)
|
||||
|
||||
@@ -22,15 +22,32 @@ namespace NzbDrone.Api.Calendar
|
||||
|
||||
private Response GetCalendarFeed()
|
||||
{
|
||||
var start = DateTime.Today.AddDays(-7);
|
||||
var end = DateTime.Today.AddDays(28);
|
||||
var pastDays = 7;
|
||||
var futureDays = 28;
|
||||
var start = DateTime.Today.AddDays(-pastDays);
|
||||
var end = DateTime.Today.AddDays(futureDays);
|
||||
|
||||
// TODO: Remove start/end parameters in v3, they don't work well for iCal
|
||||
var queryStart = Request.Query.Start;
|
||||
var queryEnd = Request.Query.End;
|
||||
var queryPastDays = Request.Query.PastDays;
|
||||
var queryFutureDays = Request.Query.FutureDays;
|
||||
|
||||
if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value);
|
||||
if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value);
|
||||
|
||||
if (queryPastDays.HasValue)
|
||||
{
|
||||
pastDays = int.Parse(queryPastDays.Value);
|
||||
start = DateTime.Today.AddDays(-pastDays);
|
||||
}
|
||||
|
||||
if (queryFutureDays.HasValue)
|
||||
{
|
||||
futureDays = int.Parse(queryFutureDays.Value);
|
||||
end = DateTime.Today.AddDays(futureDays);
|
||||
}
|
||||
|
||||
var episodes = _episodeService.EpisodesBetweenDates(start, end, false);
|
||||
var icalCalendar = new iCalendar();
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Api.Validation;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace NzbDrone.Api.Config
|
||||
@@ -16,8 +17,7 @@ namespace NzbDrone.Api.Config
|
||||
.GreaterThanOrEqualTo(0);
|
||||
|
||||
SharedValidator.RuleFor(c => c.RssSyncInterval)
|
||||
.InclusiveBetween(10, 120)
|
||||
.When(c => c.RssSyncInterval > 0);
|
||||
.IsValidRssSyncInterval();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,16 +52,16 @@ namespace NzbDrone.Api.EpisodeFiles
|
||||
|
||||
private List<EpisodeFileResource> GetEpisodeFiles()
|
||||
{
|
||||
var seriesId = (int?)Request.Query.SeriesId;
|
||||
|
||||
if (seriesId == null)
|
||||
if (!Request.Query.SeriesId.HasValue)
|
||||
{
|
||||
throw new BadRequestException("seriesId is missing");
|
||||
}
|
||||
|
||||
var series = _seriesService.GetSeries(seriesId.Value);
|
||||
var seriesId = (int)Request.Query.SeriesId;
|
||||
|
||||
return _mediaFileService.GetFilesBySeries(seriesId.Value)
|
||||
var series = _seriesService.GetSeries(seriesId);
|
||||
|
||||
return _mediaFileService.GetFilesBySeries(seriesId)
|
||||
.Select(f => MapToResource(series, f)).ToList();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,14 +20,14 @@ namespace NzbDrone.Api.Episodes
|
||||
|
||||
private List<EpisodeResource> GetEpisodes()
|
||||
{
|
||||
var seriesId = (int?)Request.Query.SeriesId;
|
||||
|
||||
if (seriesId == null)
|
||||
if (!Request.Query.SeriesId.HasValue)
|
||||
{
|
||||
throw new BadRequestException("seriesId is missing");
|
||||
}
|
||||
|
||||
var resources = ToListResource(_episodeService.GetEpisodeBySeries(seriesId.Value));
|
||||
var seriesId = (int)Request.Query.SeriesId;
|
||||
|
||||
var resources = ToListResource(_episodeService.GetEpisodeBySeries(seriesId));
|
||||
|
||||
return resources;
|
||||
}
|
||||
@@ -36,5 +36,10 @@ namespace NzbDrone.Api.Episodes
|
||||
{
|
||||
_episodeService.SetEpisodeMonitored(episodeResource.Id, episodeResource.Monitored);
|
||||
}
|
||||
|
||||
protected override List<EpisodeResource> LoadSeries(List<EpisodeResource> resources)
|
||||
{
|
||||
return resources;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,8 @@ namespace NzbDrone.Api.Episodes
|
||||
{
|
||||
var resources = base.ToListResource(modelList);
|
||||
|
||||
return resources.LoadSubtype<EpisodeResource, SeriesResource, Core.Tv.Series>(e => e.SeriesId, _seriesService.GetSeries).ToList();
|
||||
return LoadSeries(resources);
|
||||
|
||||
}
|
||||
|
||||
public void Handle(EpisodeGrabbedEvent message)
|
||||
@@ -100,5 +101,10 @@ namespace NzbDrone.Api.Episodes
|
||||
BroadcastResourceChange(ModelAction.Updated, episode.Id);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual List<EpisodeResource> LoadSeries(List<EpisodeResource> resources)
|
||||
{
|
||||
return resources.LoadSubtype<EpisodeResource, SeriesResource, Core.Tv.Series>(e => e.SeriesId, _seriesService.GetSeries).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,18 +18,13 @@ namespace NzbDrone.Api.Episodes
|
||||
|
||||
private List<RenameEpisodeResource> GetEpisodes()
|
||||
{
|
||||
int seriesId;
|
||||
|
||||
if (Request.Query.SeriesId.HasValue)
|
||||
{
|
||||
seriesId = (int)Request.Query.SeriesId;
|
||||
}
|
||||
|
||||
else
|
||||
if (!Request.Query.SeriesId.HasValue)
|
||||
{
|
||||
throw new BadRequestException("seriesId is missing");
|
||||
}
|
||||
|
||||
var seriesId = (int)Request.Query.SeriesId;
|
||||
|
||||
if (Request.Query.SeasonNumber.HasValue)
|
||||
{
|
||||
var seasonNumber = (int)Request.Query.SeasonNumber;
|
||||
|
||||
@@ -30,10 +30,13 @@ namespace NzbDrone.Api.ManualImport
|
||||
|
||||
private ManualImportResource AddQualityWeight(ManualImportResource item)
|
||||
{
|
||||
item.QualityWeight = Quality.DefaultQualityDefinitions.Single(q => q.Quality == item.Quality.Quality).Weight;
|
||||
item.QualityWeight += item.Quality.Revision.Real * 10;
|
||||
item.QualityWeight += item.Quality.Revision.Version;
|
||||
|
||||
if (item.Quality != null)
|
||||
{
|
||||
item.QualityWeight = Quality.DefaultQualityDefinitions.Single(q => q.Quality == item.Quality.Quality).Weight;
|
||||
item.QualityWeight += item.Quality.Revision.Real * 10;
|
||||
item.QualityWeight += item.Quality.Revision.Version;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +40,9 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="FluentValidation, Version=5.5.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\FluentValidation.5.5.0.0\lib\Net40\FluentValidation.dll</HintPath>
|
||||
<Reference Include="FluentValidation, Version=6.0.2.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentValidation.6.0.2.0\lib\portable-net40+sl50+wp80+win8+wpa81\FluentValidation.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Nancy, Version=0.23.2.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
@@ -230,6 +230,7 @@
|
||||
<Compile Include="Series\SeasonStatisticsResource.cs" />
|
||||
<Compile Include="System\Backup\BackupModule.cs" />
|
||||
<Compile Include="System\Backup\BackupResource.cs" />
|
||||
<Compile Include="System\Statistics\StatisticsModule.cs" />
|
||||
<Compile Include="System\Tasks\TaskModule.cs" />
|
||||
<Compile Include="System\Tasks\TaskResource.cs" />
|
||||
<Compile Include="System\SystemModule.cs" />
|
||||
@@ -238,6 +239,7 @@
|
||||
<Compile Include="TinyIoCNancyBootstrapper.cs" />
|
||||
<Compile Include="Update\UpdateModule.cs" />
|
||||
<Compile Include="Update\UpdateResource.cs" />
|
||||
<Compile Include="Validation\RssSyncIntervalValidator.cs" />
|
||||
<Compile Include="Validation\EmptyCollectionValidator.cs" />
|
||||
<Compile Include="Validation\RuleBuilderExtensions.cs" />
|
||||
<Compile Include="Wanted\CutoffModule.cs" />
|
||||
@@ -270,7 +272,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
|
||||
@@ -10,11 +10,11 @@ using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.SeriesStats;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Api.Validation;
|
||||
using NzbDrone.Api.Mapping;
|
||||
using NzbDrone.Core.Tv.Events;
|
||||
using NzbDrone.Core.Validation.Paths;
|
||||
using NzbDrone.Core.DataAugmentation.Scene;
|
||||
using NzbDrone.Core.Validation;
|
||||
using NzbDrone.SignalR;
|
||||
|
||||
namespace NzbDrone.Api.Series
|
||||
@@ -43,7 +43,8 @@ namespace NzbDrone.Api.Series
|
||||
SeriesPathValidator seriesPathValidator,
|
||||
SeriesExistsValidator seriesExistsValidator,
|
||||
DroneFactoryValidator droneFactoryValidator,
|
||||
SeriesAncestorValidator seriesAncestorValidator
|
||||
SeriesAncestorValidator seriesAncestorValidator,
|
||||
ProfileExistsValidator profileExistsValidator
|
||||
)
|
||||
: base(signalRBroadcaster)
|
||||
{
|
||||
@@ -59,7 +60,7 @@ namespace NzbDrone.Api.Series
|
||||
UpdateResource = UpdateSeries;
|
||||
DeleteResource = DeleteSeries;
|
||||
|
||||
SharedValidator.RuleFor(s => s.ProfileId).ValidId();
|
||||
Validation.RuleBuilderExtensions.ValidId(SharedValidator.RuleFor(s => s.ProfileId));
|
||||
|
||||
SharedValidator.RuleFor(s => s.Path)
|
||||
.Cascade(CascadeMode.StopOnFirstFailure)
|
||||
@@ -70,6 +71,8 @@ namespace NzbDrone.Api.Series
|
||||
.SetValidator(seriesAncestorValidator)
|
||||
.When(s => !s.Path.IsNullOrWhiteSpace());
|
||||
|
||||
SharedValidator.RuleFor(s => s.ProfileId).SetValidator(profileExistsValidator);
|
||||
|
||||
PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace());
|
||||
PostValidator.RuleFor(s => s.RootFolderPath).IsValidPath().When(s => s.Path.IsNullOrWhiteSpace());
|
||||
PostValidator.RuleFor(s => s.Title).NotEmpty();
|
||||
|
||||
62
src/NzbDrone.Api/System/Statistics/StatisticsModule.cs
Normal file
62
src/NzbDrone.Api/System/Statistics/StatisticsModule.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Nancy;
|
||||
using Nancy.Routing;
|
||||
using NLog;
|
||||
using NzbDrone.Api.Extensions;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.History;
|
||||
using NzbDrone.Core.Lifecycle;
|
||||
using NzbDrone.Core.Statistics;
|
||||
|
||||
namespace NzbDrone.Api.System
|
||||
{
|
||||
public class StatisticsModule : NzbDroneApiModule
|
||||
{
|
||||
private readonly IStatisticsService _statisticsService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public StatisticsModule(IStatisticsService statisticsService, Logger logger)
|
||||
: base("system/statistics")
|
||||
{
|
||||
_statisticsService = statisticsService;
|
||||
_logger = logger;
|
||||
|
||||
Get["/"] = x => GetGlobalStatistics();
|
||||
Get["/indexer"] = x => GetIndexerStatistics();
|
||||
}
|
||||
|
||||
private Response GetGlobalStatistics()
|
||||
{
|
||||
return new
|
||||
{
|
||||
Generated = DateTime.UtcNow,
|
||||
Uptime = GetUpTime(),
|
||||
History = _statisticsService.GetGlobalStatistics()
|
||||
}.AsResponse();
|
||||
}
|
||||
|
||||
private Response GetIndexerStatistics()
|
||||
{
|
||||
var stats = _statisticsService.GetIndexerStatistics();
|
||||
|
||||
return stats.AsResponse();
|
||||
}
|
||||
|
||||
private TimeSpan? GetUpTime()
|
||||
{
|
||||
try
|
||||
{
|
||||
return DateTime.Now - global::System.Diagnostics.Process.GetCurrentProcess().StartTime;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.DebugException("Failed to get uptime", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/NzbDrone.Api/Validation/RssSyncIntervalValidator.cs
Normal file
34
src/NzbDrone.Api/Validation/RssSyncIntervalValidator.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using FluentValidation.Validators;
|
||||
|
||||
namespace NzbDrone.Api.Validation
|
||||
{
|
||||
public class RssSyncIntervalValidator : PropertyValidator
|
||||
{
|
||||
public RssSyncIntervalValidator()
|
||||
: base("Must be between 10 and 120 or 0 to disable")
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool IsValid(PropertyValidatorContext context)
|
||||
{
|
||||
if (context.PropertyValue == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var value = (int)context.PropertyValue;
|
||||
|
||||
if (value == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value >= 10 && value <= 120)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,5 +31,10 @@ namespace NzbDrone.Api.Validation
|
||||
{
|
||||
return ruleBuilder.SetValidator(new EmptyCollectionValidator<TProp>());
|
||||
}
|
||||
|
||||
public static IRuleBuilderOptions<T, int> IsValidRssSyncInterval<T>(this IRuleBuilder<T, int> ruleBuilder)
|
||||
{
|
||||
return ruleBuilder.SetValidator(new RssSyncIntervalValidator());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="DDay.iCal" version="1.0.2.575" targetFramework="net40" />
|
||||
<package id="FluentValidation" version="5.5.0.0" targetFramework="net40" />
|
||||
<package id="FluentValidation" version="6.0.2.0" targetFramework="net40" />
|
||||
<package id="Nancy" version="0.23.2" targetFramework="net40" />
|
||||
<package id="Nancy.Authentication.Basic" version="0.23.2" targetFramework="net40" />
|
||||
<package id="Nancy.Authentication.Forms" version="0.23.2" targetFramework="net40" />
|
||||
<package id="Newtonsoft.Json" version="6.0.6" targetFramework="net40" />
|
||||
<package id="NLog" version="2.1.0" targetFramework="net40" />
|
||||
<package id="ValueInjecter" version="2.3.3" targetFramework="net40" />
|
||||
</packages>
|
||||
</packages>
|
||||
@@ -37,12 +37,12 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="FluentAssertions, Version=3.4.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.3.4.0\lib\net40\FluentAssertions.dll</HintPath>
|
||||
<Reference Include="FluentAssertions, Version=4.2.1.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.4.2.1\lib\net40\FluentAssertions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FluentAssertions.Core, Version=3.4.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.3.4.0\lib\net40\FluentAssertions.Core.dll</HintPath>
|
||||
<Reference Include="FluentAssertions.Core, Version=4.2.1.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.4.2.1\lib\net40\FluentAssertions.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="nunit.framework, Version=2.6.3.13283, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
|
||||
@@ -104,7 +104,6 @@
|
||||
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent Condition="('$(OS)' == 'Windows_NT')">
|
||||
xcopy /s /y "$(SolutionDir)\..\_output\NzbDrone.Mono.*" "$(TargetDir)"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="FluentAssertions" version="3.4.0" targetFramework="net40" />
|
||||
<package id="FluentAssertions" version="4.2.1" targetFramework="net40" />
|
||||
<package id="Moq" version="4.0.10827" />
|
||||
<package id="NBuilder" version="3.0.1.1" />
|
||||
<package id="NLog" version="2.1.0" targetFramework="net40" />
|
||||
|
||||
@@ -38,12 +38,12 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="FluentAssertions, Version=3.4.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.3.4.0\lib\net40\FluentAssertions.dll</HintPath>
|
||||
<Reference Include="FluentAssertions, Version=4.2.1.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.4.2.1\lib\net40\FluentAssertions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FluentAssertions.Core, Version=3.4.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.3.4.0\lib\net40\FluentAssertions.Core.dll</HintPath>
|
||||
<Reference Include="FluentAssertions.Core, Version=4.2.1.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.4.2.1\lib\net40\FluentAssertions.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="nunit.framework, Version=2.6.3.13283, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
|
||||
@@ -95,7 +95,6 @@
|
||||
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
@@ -103,4 +102,4 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="FluentAssertions" version="3.4.0" targetFramework="net40" />
|
||||
<package id="FluentAssertions" version="4.2.1" targetFramework="net40" />
|
||||
<package id="NLog" version="2.1.0" targetFramework="net40" />
|
||||
<package id="NUnit" version="2.6.3" targetFramework="net40" />
|
||||
<package id="Selenium.Support" version="2.48.0" targetFramework="net40" />
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
{
|
||||
MonoOnly();
|
||||
|
||||
Subject.VerificationMode.Should().Be(DiskTransferVerificationMode.Transactional);
|
||||
Subject.VerificationMode.Should().Be(DiskTransferVerificationMode.TryTransactional);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -199,9 +199,6 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.CopyFile(_sourcePath, _targetPath, false), Times.Once());
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.GetFileSize(It.IsAny<string>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -213,9 +210,6 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.MoveFile(_sourcePath, _targetPath, false), Times.Once());
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.GetFileSize(It.IsAny<string>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -377,6 +371,52 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
ExceptionVerification.ExpectedErrors(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void mode_transactional_should_move_and_verify_if_same_folder()
|
||||
{
|
||||
Subject.VerificationMode = DiskTransferVerificationMode.Transactional;
|
||||
|
||||
var targetPath = _sourcePath + ".test";
|
||||
|
||||
var result = Subject.TransferFile(_sourcePath, targetPath, TransferMode.Move);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.TryCreateHardLink(_sourcePath, _backupPath), Times.Never());
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.CopyFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>()), Times.Never());
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.MoveFile(_sourcePath, targetPath, false), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void mode_trytransactional_should_revert_to_verifyonly_if_hardlink_fails()
|
||||
{
|
||||
Subject.VerificationMode = DiskTransferVerificationMode.TryTransactional;
|
||||
|
||||
WithFailedHardlink();
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.MoveFile(_sourcePath, _targetPath, false))
|
||||
.Callback(() =>
|
||||
{
|
||||
WithExistingFile(_sourcePath, false);
|
||||
WithExistingFile(_targetPath, true);
|
||||
});
|
||||
|
||||
var result = Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.TryCreateHardLink(_sourcePath, _backupPath), Times.Once());
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.CopyFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>()), Times.Never());
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.MoveFile(_sourcePath, _targetPath, false), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void mode_transactional_should_delete_old_backup_on_move()
|
||||
{
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NLog;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Http.Dispatchers;
|
||||
using NzbDrone.Common.TPL;
|
||||
using NzbDrone.Test.Common;
|
||||
using NzbDrone.Test.Common.Categories;
|
||||
using NLog;
|
||||
using NzbDrone.Common.TPL;
|
||||
using Moq;
|
||||
using NzbDrone.Common.Http.Dispatchers;
|
||||
|
||||
namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
@@ -309,30 +311,70 @@ namespace NzbDrone.Common.Test.Http
|
||||
.Verify(v => v.PostResponse(It.IsAny<HttpResponse>()), Times.Once());
|
||||
}
|
||||
|
||||
public void should_parse_malformed_cloudflare_cookie()
|
||||
[TestCase("en-US")]
|
||||
[TestCase("es-ES")]
|
||||
public void should_parse_malformed_cloudflare_cookie(string culture)
|
||||
{
|
||||
// the date is bad in the below - should be 13-Jul-2016
|
||||
string malformedCookie = @"__cfduid=d29e686a9d65800021c66faca0a29b4261436890790; expires=Wed, 13-Jul-16 16:19:50 GMT; path=/; HttpOnly";
|
||||
string url = "http://eu.httpbin.org/response-headers?Set-Cookie=" +
|
||||
System.Uri.EscapeUriString(malformedCookie);
|
||||
var origCulture = Thread.CurrentThread.CurrentCulture;
|
||||
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(culture);
|
||||
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(culture);
|
||||
try
|
||||
{
|
||||
// the date is bad in the below - should be 13-Jul-2016
|
||||
string malformedCookie = @"__cfduid=d29e686a9d65800021c66faca0a29b4261436890790; expires=Wed, 13-Jul-16 16:19:50 GMT; path=/; HttpOnly";
|
||||
string url = "http://eu.httpbin.org/response-headers?Set-Cookie=" +
|
||||
System.Uri.EscapeUriString(malformedCookie);
|
||||
|
||||
var requestSet = new HttpRequest(url);
|
||||
requestSet.AllowAutoRedirect = false;
|
||||
requestSet.StoreResponseCookie = true;
|
||||
var requestSet = new HttpRequest(url);
|
||||
requestSet.AllowAutoRedirect = false;
|
||||
requestSet.StoreResponseCookie = true;
|
||||
|
||||
var responseSet = Subject.Get(requestSet);
|
||||
var responseSet = Subject.Get(requestSet);
|
||||
|
||||
var request = new HttpRequest("http://eu.httpbin.org/get");
|
||||
var request = new HttpRequest("http://eu.httpbin.org/get");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
|
||||
response.Resource.Headers.Should().ContainKey("Cookie");
|
||||
response.Resource.Headers.Should().ContainKey("Cookie");
|
||||
|
||||
var cookie = response.Resource.Headers["Cookie"].ToString();
|
||||
var cookie = response.Resource.Headers["Cookie"].ToString();
|
||||
|
||||
cookie.Should().Contain("__cfduid=d29e686a9d65800021c66faca0a29b4261436890790");
|
||||
cookie.Should().Contain("__cfduid=d29e686a9d65800021c66faca0a29b4261436890790");
|
||||
|
||||
ExceptionVerification.IgnoreErrors();
|
||||
ExceptionVerification.IgnoreErrors();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Thread.CurrentThread.CurrentCulture = origCulture;
|
||||
Thread.CurrentThread.CurrentUICulture = origCulture;
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase("lang_code=en; expires=Fri, 23-Dec-2016 18:09:14 GMT; Max-Age=31536000; path=/; domain=.abc.com")]
|
||||
public void should_reject_malformed_domain_cookie(string malformedCookie)
|
||||
{
|
||||
try
|
||||
{
|
||||
// the date is bad in the below - should be 13-Jul-2016
|
||||
string url = "http://eu.httpbin.org/response-headers?Set-Cookie=" + Uri.EscapeUriString(malformedCookie);
|
||||
|
||||
var requestSet = new HttpRequest(url);
|
||||
requestSet.AllowAutoRedirect = false;
|
||||
requestSet.StoreResponseCookie = true;
|
||||
|
||||
var responseSet = Subject.Get(requestSet);
|
||||
|
||||
var request = new HttpRequest("http://eu.httpbin.org/get");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
|
||||
response.Resource.Headers.Should().NotContainKey("Cookie");
|
||||
|
||||
ExceptionVerification.IgnoreErrors();
|
||||
}
|
||||
finally
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,11 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
// Indexer Urls
|
||||
[TestCase(@"https://iptorrents.com/torrents/rss?u=mySecret;tp=mySecret;l5;download")]
|
||||
[TestCase(@"http://rss.torrentleech.org/mySecret")]
|
||||
[TestCase(@"http://rss.torrentleech.org/rss/download/12345/01233210/filename.torrent")]
|
||||
[TestCase(@"http://www.bitmetv.org/rss.php?uid=mySecret&passkey=mySecret")]
|
||||
[TestCase(@"https://rss.omgwtfnzbs.org/rss-search.php?catid=19,20&user=sonarr&api=mySecret&eng=1")]
|
||||
[TestCase(@"https://dognzb.cr/fetch/2b51db35e1912ffc138825a12b9933d2/2b51db35e1910123321025a12b9933d2")]
|
||||
[TestCase(@"https://baconbits.org/feeds.php?feed=torrents_tv&user=12345&auth=2b51db35e1910123321025a12b9933d2&passkey=mySecret&authkey=2b51db35e1910123321025a12b9933d2")]
|
||||
// NzbGet
|
||||
[TestCase(@"{ ""Name"" : ""ControlUsername"", ""Value"" : ""mySecret"" }, { ""Name"" : ""ControlPassword"", ""Value"" : ""mySecret"" }, ")]
|
||||
[TestCase(@"{ ""Name"" : ""Server1.Username"", ""Value"" : ""mySecret"" }, { ""Name"" : ""Server1.Password"", ""Value"" : ""mySecret"" }, ")]
|
||||
@@ -44,6 +47,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
||||
|
||||
cleansedMessage.Should().NotContain("mySecret");
|
||||
cleansedMessage.Should().NotContain("01233210");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
@@ -37,12 +37,12 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="FluentAssertions, Version=3.4.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.3.4.0\lib\net40\FluentAssertions.dll</HintPath>
|
||||
<Reference Include="FluentAssertions, Version=4.2.1.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.4.2.1\lib\net40\FluentAssertions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FluentAssertions.Core, Version=3.4.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.3.4.0\lib\net40\FluentAssertions.Core.dll</HintPath>
|
||||
<Reference Include="FluentAssertions.Core, Version=4.2.1.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.4.2.1\lib\net40\FluentAssertions.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="nunit.framework, Version=2.6.3.13283, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
|
||||
@@ -141,11 +141,10 @@
|
||||
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent Condition="'$(Configuration)|$(OS)' == 'Debug|Windows_NT'">xcopy /s /y "$(SolutionDir)\ExternalModules\CurlSharp\libs\i386\*" "$(TargetDir)"</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace NzbDrone.Common.Test
|
||||
[TestCase("/rooted/linux/path", OsPathKind.Unix)]
|
||||
[TestCase("/", OsPathKind.Unix)]
|
||||
[TestCase("linux/path", OsPathKind.Unix)]
|
||||
[TestCase(@"Castle:unrooted+linux+path", OsPathKind.Unknown)]
|
||||
public void should_auto_detect_kind(string path, OsPathKind kind)
|
||||
{
|
||||
var result = new OsPath(path);
|
||||
@@ -94,6 +95,8 @@ namespace NzbDrone.Common.Test
|
||||
[TestCase(@"rooted\windows\path")]
|
||||
[TestCase(@"path")]
|
||||
[TestCase("linux/path")]
|
||||
[TestCase(@"Castle:unrooted+linux+path")]
|
||||
[TestCase(@"C:unrooted+linux+path")]
|
||||
public void should_detect_unrooted_ospaths(string path)
|
||||
{
|
||||
var osPath = new OsPath(path);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="FluentAssertions" version="3.4.0" targetFramework="net40" />
|
||||
<package id="FluentAssertions" version="4.2.1" targetFramework="net40" />
|
||||
<package id="Moq" version="4.0.10827" />
|
||||
<package id="NLog" version="2.1.0" targetFramework="net40" />
|
||||
<package id="NUnit" version="2.6.3" targetFramework="net40" />
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace NzbDrone.Common.Disk
|
||||
{
|
||||
None,
|
||||
VerifyOnly,
|
||||
TryTransactional,
|
||||
Transactional
|
||||
}
|
||||
|
||||
@@ -40,7 +41,7 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
// TODO: Atm we haven't seen partial transfers on windows so we disable verified transfer.
|
||||
// (If enabled in the future, be sure to check specifically for ReFS, which doesn't support hardlinks.)
|
||||
VerificationMode = OsInfo.IsWindows ? DiskTransferVerificationMode.VerifyOnly : DiskTransferVerificationMode.Transactional;
|
||||
VerificationMode = OsInfo.IsWindows ? DiskTransferVerificationMode.VerifyOnly : DiskTransferVerificationMode.TryTransactional;
|
||||
}
|
||||
|
||||
public TransferMode TransferFolder(string sourcePath, string targetPath, TransferMode mode, bool verified = true)
|
||||
@@ -48,11 +49,6 @@ namespace NzbDrone.Common.Disk
|
||||
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
|
||||
Ensure.That(targetPath, () => targetPath).IsValidPath();
|
||||
|
||||
if (VerificationMode != DiskTransferVerificationMode.Transactional)
|
||||
{
|
||||
verified = false;
|
||||
}
|
||||
|
||||
if (!_diskProvider.FolderExists(targetPath))
|
||||
{
|
||||
_diskProvider.CreateFolder(targetPath);
|
||||
@@ -85,12 +81,14 @@ namespace NzbDrone.Common.Disk
|
||||
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
|
||||
Ensure.That(targetPath, () => targetPath).IsValidPath();
|
||||
|
||||
if (VerificationMode != DiskTransferVerificationMode.Transactional)
|
||||
if (VerificationMode != DiskTransferVerificationMode.Transactional && VerificationMode != DiskTransferVerificationMode.TryTransactional)
|
||||
{
|
||||
verified = false;
|
||||
}
|
||||
|
||||
_logger.Debug("{0} [{1}] > [{2}]", mode, sourcePath, targetPath);
|
||||
|
||||
var originalSize = _diskProvider.GetFileSize(sourcePath);
|
||||
|
||||
if (sourcePath == targetPath)
|
||||
{
|
||||
@@ -127,6 +125,15 @@ namespace NzbDrone.Common.Disk
|
||||
return TransferMode.None;
|
||||
}
|
||||
|
||||
if (sourcePath.GetParentPath() == targetPath.GetParentPath())
|
||||
{
|
||||
if (mode.HasFlag(TransferMode.Move))
|
||||
{
|
||||
TryMoveFileVerified(sourcePath, targetPath, originalSize);
|
||||
return TransferMode.Move;
|
||||
}
|
||||
}
|
||||
|
||||
if (sourcePath.IsParentPath(targetPath))
|
||||
{
|
||||
throw new IOException(string.Format("Destination cannot be a child of the source [{0}] => [{1}]", sourcePath, targetPath));
|
||||
@@ -151,7 +158,7 @@ namespace NzbDrone.Common.Disk
|
||||
{
|
||||
if (mode.HasFlag(TransferMode.Copy))
|
||||
{
|
||||
if (TryCopyFile(sourcePath, targetPath))
|
||||
if (TryCopyFileTransactional(sourcePath, targetPath, originalSize))
|
||||
{
|
||||
return TransferMode.Copy;
|
||||
}
|
||||
@@ -159,7 +166,7 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
if (mode.HasFlag(TransferMode.Move))
|
||||
{
|
||||
if (TryMoveFile(sourcePath, targetPath))
|
||||
if (TryMoveFileTransactional(sourcePath, targetPath, originalSize))
|
||||
{
|
||||
return TransferMode.Move;
|
||||
}
|
||||
@@ -167,50 +174,18 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
throw new IOException(string.Format("Failed to completely transfer [{0}] to [{1}], aborting.", sourcePath, targetPath));
|
||||
}
|
||||
else if (VerificationMode == DiskTransferVerificationMode.VerifyOnly)
|
||||
else if (VerificationMode != DiskTransferVerificationMode.None)
|
||||
{
|
||||
var originalSize = _diskProvider.GetFileSize(sourcePath);
|
||||
|
||||
if (mode.HasFlag(TransferMode.Copy))
|
||||
{
|
||||
try
|
||||
{
|
||||
_diskProvider.CopyFile(sourcePath, targetPath);
|
||||
|
||||
var targetSize = _diskProvider.GetFileSize(targetPath);
|
||||
if (targetSize != originalSize)
|
||||
{
|
||||
throw new IOException(string.Format("File copy incomplete. [{0}] was {1} bytes long instead of {2} bytes.", targetPath, targetSize, originalSize));
|
||||
}
|
||||
|
||||
return TransferMode.Copy;
|
||||
}
|
||||
catch
|
||||
{
|
||||
RollbackCopy(sourcePath, targetPath);
|
||||
throw;
|
||||
}
|
||||
TryCopyFileVerified(sourcePath, targetPath, originalSize);
|
||||
return TransferMode.Copy;
|
||||
}
|
||||
|
||||
if (mode.HasFlag(TransferMode.Move))
|
||||
{
|
||||
try
|
||||
{
|
||||
_diskProvider.MoveFile(sourcePath, targetPath);
|
||||
|
||||
var targetSize = _diskProvider.GetFileSize(targetPath);
|
||||
if (targetSize != originalSize)
|
||||
{
|
||||
throw new IOException(string.Format("File copy incomplete, data loss may have occured. [{0}] was {1} bytes long instead of the expected {2}.", targetPath, targetSize, originalSize));
|
||||
}
|
||||
|
||||
return TransferMode.Move;
|
||||
}
|
||||
catch
|
||||
{
|
||||
RollbackPartialMove(sourcePath, targetPath);
|
||||
throw;
|
||||
}
|
||||
TryMoveFileVerified(sourcePath, targetPath, originalSize);
|
||||
return TransferMode.Move;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -310,10 +285,8 @@ namespace NzbDrone.Common.Disk
|
||||
Thread.Sleep(3000);
|
||||
}
|
||||
|
||||
private bool TryCopyFile(string sourcePath, string targetPath)
|
||||
private bool TryCopyFileTransactional(string sourcePath, string targetPath, long originalSize)
|
||||
{
|
||||
var originalSize = _diskProvider.GetFileSize(sourcePath);
|
||||
|
||||
var tempTargetPath = targetPath + ".partial~";
|
||||
|
||||
if (_diskProvider.FileExists(tempTargetPath))
|
||||
@@ -367,10 +340,8 @@ namespace NzbDrone.Common.Disk
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryMoveFile(string sourcePath, string targetPath)
|
||||
private bool TryMoveFileTransactional(string sourcePath, string targetPath, long originalSize)
|
||||
{
|
||||
var originalSize = _diskProvider.GetFileSize(sourcePath);
|
||||
|
||||
var backupPath = sourcePath + ".backup~";
|
||||
var tempTargetPath = targetPath + ".partial~";
|
||||
|
||||
@@ -423,16 +394,63 @@ namespace NzbDrone.Common.Disk
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Trace("Hardlink move failed, reverting to copy.");
|
||||
if (TryCopyFile(sourcePath, targetPath))
|
||||
if (VerificationMode == DiskTransferVerificationMode.Transactional)
|
||||
{
|
||||
_logger.Trace("Copy succeeded, deleting source.");
|
||||
_diskProvider.DeleteFile(sourcePath);
|
||||
_logger.Trace("Hardlink move failed, reverting to copy.");
|
||||
if (TryCopyFileTransactional(sourcePath, targetPath, originalSize))
|
||||
{
|
||||
_logger.Trace("Copy succeeded, deleting source.");
|
||||
_diskProvider.DeleteFile(sourcePath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Trace("Hardlink move failed, reverting to move.");
|
||||
TryMoveFileVerified(sourcePath, targetPath, originalSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
_logger.Trace("Copy failed.");
|
||||
_logger.Trace("Move failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
private void TryCopyFileVerified(string sourcePath, string targetPath, long originalSize)
|
||||
{
|
||||
try
|
||||
{
|
||||
_diskProvider.CopyFile(sourcePath, targetPath);
|
||||
|
||||
var targetSize = _diskProvider.GetFileSize(targetPath);
|
||||
if (targetSize != originalSize)
|
||||
{
|
||||
throw new IOException(string.Format("File copy incomplete. [{0}] was {1} bytes long instead of {2} bytes.", targetPath, targetSize, originalSize));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
RollbackCopy(sourcePath, targetPath);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void TryMoveFileVerified(string sourcePath, string targetPath, long originalSize)
|
||||
{
|
||||
try
|
||||
{
|
||||
_diskProvider.MoveFile(sourcePath, targetPath);
|
||||
|
||||
var targetSize = _diskProvider.GetFileSize(targetPath);
|
||||
if (targetSize != originalSize)
|
||||
{
|
||||
throw new IOException(string.Format("File move incomplete, data loss may have occurred. [{0}] was {1} bytes long instead of the expected {2}.", targetPath, targetSize, originalSize));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
RollbackPartialMove(sourcePath, targetPath);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace NzbDrone.Common.Disk
|
||||
{
|
||||
return OsPathKind.Unix;
|
||||
}
|
||||
if (path.Contains(':') || path.Contains('\\'))
|
||||
if (HasWindowsDriveLetter(path) || path.Contains('\\'))
|
||||
{
|
||||
return OsPathKind.Windows;
|
||||
}
|
||||
@@ -55,6 +55,15 @@ namespace NzbDrone.Common.Disk
|
||||
return OsPathKind.Unknown;
|
||||
}
|
||||
|
||||
private static bool HasWindowsDriveLetter(string path)
|
||||
{
|
||||
if (path.Length < 2) return false;
|
||||
if (!char.IsLetter(path[0]) || path[1] != ':') return false;
|
||||
if (path.Length > 2 && path[2] != '\\' && path[2] != '/') return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string FixSlashes(string path, OsPathKind kind)
|
||||
{
|
||||
switch (kind)
|
||||
@@ -97,7 +106,7 @@ namespace NzbDrone.Common.Disk
|
||||
{
|
||||
if (IsWindowsPath)
|
||||
{
|
||||
return _path.StartsWith(@"\\") || _path.Contains(':');
|
||||
return _path.StartsWith(@"\\") || HasWindowsDriveLetter(_path);
|
||||
}
|
||||
if (IsUnixPath)
|
||||
{
|
||||
@@ -153,6 +162,14 @@ namespace NzbDrone.Common.Disk
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
return _path.IsPathValid();
|
||||
}
|
||||
}
|
||||
|
||||
private int GetFileNameIndex()
|
||||
{
|
||||
if (_path.Length < 2)
|
||||
|
||||
@@ -92,5 +92,13 @@ namespace NzbDrone.Common.Extensions
|
||||
|
||||
return "\"" + text + "\"";
|
||||
}
|
||||
|
||||
public static byte[] HexToByteArray(this string input)
|
||||
{
|
||||
return Enumerable.Range(0, input.Length)
|
||||
.Where(x => x%2 == 0)
|
||||
.Select(x => Convert.ToByte(input.Substring(x, 2), 16))
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
@@ -164,7 +165,14 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
var setCookie = webHeaderCollection.Get("Set-Cookie");
|
||||
if (setCookie != null && setCookie.Length > 0 && cookies != null)
|
||||
{
|
||||
cookies.SetCookies(request.Url, FixSetCookieHeader(setCookie));
|
||||
try
|
||||
{
|
||||
cookies.SetCookies(request.Url, FixSetCookieHeader(setCookie));
|
||||
}
|
||||
catch (CookieException ex)
|
||||
{
|
||||
_logger.Debug("Rejected cookie {0}: {1}", ex.InnerException.Message, setCookie);
|
||||
}
|
||||
}
|
||||
|
||||
return webHeaderCollection;
|
||||
@@ -175,9 +183,15 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
// fix up the date if it was malformed
|
||||
var setCookieClean = ExpiryDate.Replace(setCookie, delegate(Match match)
|
||||
{
|
||||
string format = "ddd, dd-MMM-yyyy HH:mm:ss";
|
||||
DateTime dt = Convert.ToDateTime(match.Groups[2].Value);
|
||||
return match.Groups[1].Value + dt.ToUniversalTime().ToString(format) + " GMT";
|
||||
string shortFormat = "ddd, dd-MMM-yy HH:mm:ss";
|
||||
string longFormat = "ddd, dd-MMM-yyyy HH:mm:ss";
|
||||
DateTime dt;
|
||||
if (DateTime.TryParseExact(match.Groups[2].Value, longFormat, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out dt) ||
|
||||
DateTime.TryParseExact(match.Groups[2].Value, shortFormat, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out dt) ||
|
||||
DateTime.TryParse(match.Groups[2].Value, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out dt))
|
||||
return match.Groups[1].Value + dt.ToUniversalTime().ToString(longFormat, CultureInfo.InvariantCulture) + " GMT";
|
||||
else
|
||||
return match.Value;
|
||||
});
|
||||
return setCookieClean;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,18 @@ namespace NzbDrone.Common.Http
|
||||
{
|
||||
if (response.Headers.ContainsKey("Retry-After"))
|
||||
{
|
||||
RetryAfter = TimeSpan.FromSeconds(int.Parse(response.Headers["Retry-After"].ToString()));
|
||||
var retryAfter = response.Headers["Retry-After"].ToString();
|
||||
int seconds;
|
||||
DateTime date;
|
||||
|
||||
if (int.TryParse(retryAfter, out seconds))
|
||||
{
|
||||
RetryAfter = TimeSpan.FromSeconds(seconds);
|
||||
}
|
||||
else if (DateTime.TryParse(retryAfter, out date))
|
||||
{
|
||||
RetryAfter = date.ToUniversalTime() - DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@ namespace NzbDrone.Common.Instrumentation
|
||||
private static readonly Regex[] CleansingRules = new[]
|
||||
{
|
||||
// Url
|
||||
new Regex(@"(?<=\?|&)(apikey|token|passkey|uid|api)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=\?|&)(apikey|token|passkey|auth|authkey|user|uid|api)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=\?|&)[^=]*?(username|password)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"torrentleech\.org/(?<secret>[0-9a-z]+)", 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),
|
||||
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
|
||||
|
||||
// Path
|
||||
new Regex(@"""C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
@@ -26,7 +28,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
new Regex(@"""email_(account|to|from|pwd)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// uTorrent
|
||||
new Regex(@"\[""[a-z._]*(|username|password)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"\[""[a-z._]*(username|password)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"\[""(boss_key|boss_key_salt|proxy\.proxy)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// BroadcastheNet
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
<Reference Include="System.ServiceProcess" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="ICSharpCode.SharpZipLib">
|
||||
<HintPath>..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath>
|
||||
<HintPath>..\packages\ICSharpCode.SharpZipLib.Patched.0.86.5\lib\net20\ICSharpCode.SharpZipLib.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NLog">
|
||||
<HintPath>..\packages\NLog.2.1.0\lib\net40\NLog.dll</HintPath>
|
||||
@@ -229,7 +229,6 @@
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="6.0.6" targetFramework="net40" />
|
||||
<package id="NLog" version="2.1.0" targetFramework="net40" />
|
||||
<package id="SharpZipLib" version="0.86.0" targetFramework="net40" />
|
||||
<package id="ICSharpCode.SharpZipLib.Patched" version="0.86.5" targetFramework="net40" />
|
||||
</packages>
|
||||
@@ -143,7 +143,6 @@
|
||||
<PreBuildEvent>
|
||||
</PreBuildEvent>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
@@ -151,4 +150,4 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -1,110 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.Configuration
|
||||
{
|
||||
[TestFixture]
|
||||
public class ConfigServiceFixture : DbTest<ConfigService, Config>
|
||||
public class ConfigServiceFixture : TestBase<ConfigService>
|
||||
{
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
Mocker.SetConstant<IConfigRepository>(Mocker.Resolve<ConfigRepository>());
|
||||
|
||||
Db.All<Config>().ForEach(Db.Delete);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Add_new_value_to_database()
|
||||
{
|
||||
const string key = "MY_KEY";
|
||||
const string value = "MY_VALUE";
|
||||
const string key = "RssSyncInterval";
|
||||
const int value = 12;
|
||||
|
||||
Subject.SetValue(key, value);
|
||||
Subject.GetValue(key, "").Should().Be(value);
|
||||
}
|
||||
Subject.RssSyncInterval = value;
|
||||
|
||||
[Test]
|
||||
public void Get_value_from_database()
|
||||
{
|
||||
const string key = "MY_KEY";
|
||||
const string value = "MY_VALUE";
|
||||
|
||||
|
||||
Db.Insert(new Config { Key = key, Value = value });
|
||||
Db.Insert(new Config { Key = "Other Key", Value = "OtherValue" });
|
||||
|
||||
var result = Subject.GetValue(key, "");
|
||||
|
||||
result.Should().Be(value);
|
||||
AssertUpsert(key, value);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void Get_value_should_return_default_when_no_value()
|
||||
{
|
||||
const string key = "MY_KEY";
|
||||
const string value = "MY_VALUE";
|
||||
|
||||
var result = Subject.GetValue(key, value);
|
||||
|
||||
result.Should().Be(value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void New_value_should_update_old_value_new_value()
|
||||
{
|
||||
const string key = "MY_KEY";
|
||||
const string originalValue = "OLD_VALUE";
|
||||
const string newValue = "NEW_VALUE";
|
||||
|
||||
Db.Insert(new Config { Key = key, Value = originalValue });
|
||||
|
||||
Subject.SetValue(key, newValue);
|
||||
var result = Subject.GetValue(key, "");
|
||||
|
||||
|
||||
result.Should().Be(newValue);
|
||||
AllStoredModels.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void New_value_should_update_old_value_same_value()
|
||||
{
|
||||
const string key = "MY_KEY";
|
||||
const string value = "OLD_VALUE";
|
||||
|
||||
Subject.SetValue(key, value);
|
||||
Subject.SetValue(key, value);
|
||||
var result = Subject.GetValue(key, "");
|
||||
|
||||
result.Should().Be(value);
|
||||
AllStoredModels.Should().HaveCount(1);
|
||||
Subject.RssSyncInterval.Should().Be(15);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void get_value_with_persist_should_store_default_value()
|
||||
{
|
||||
const string key = "MY_KEY";
|
||||
string value = Guid.NewGuid().ToString();
|
||||
|
||||
Subject.GetValue(key, value, persist: true).Should().Be(value);
|
||||
Subject.GetValue(key, string.Empty).Should().Be(value);
|
||||
var salt = Subject.HmacSalt;
|
||||
salt.Should().NotBeNullOrWhiteSpace();
|
||||
AssertUpsert("HmacSalt", salt);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void get_value_with_out_persist_should_not_store_default_value()
|
||||
{
|
||||
const string key = "MY_KEY";
|
||||
string value1 = Guid.NewGuid().ToString();
|
||||
string value2 = Guid.NewGuid().ToString();
|
||||
var interval = Subject.RssSyncInterval;
|
||||
interval.Should().Be(15);
|
||||
Mocker.GetMock<IConfigRepository>().Verify(c => c.Insert(It.IsAny<Config>()), Times.Never());
|
||||
}
|
||||
|
||||
Subject.GetValue(key, value1).Should().Be(value1);
|
||||
Subject.GetValue(key, value2).Should().Be(value2);
|
||||
private void AssertUpsert(string key, object value)
|
||||
{
|
||||
Mocker.GetMock<IConfigRepository>().Verify(c => c.Upsert(key.ToLowerInvariant(), value.ToString()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -114,7 +63,16 @@ namespace NzbDrone.Core.Test.Configuration
|
||||
var configProvider = Subject;
|
||||
var allProperties = typeof(ConfigService).GetProperties().Where(p => p.GetSetMethod() != null).ToList();
|
||||
|
||||
var keys = new List<string>();
|
||||
var values = new List<Config>();
|
||||
|
||||
Mocker.GetMock<IConfigRepository>().Setup(c => c.Upsert(It.IsAny<string>(), It.IsAny<string>())).Callback<string, string>((key, value) =>
|
||||
{
|
||||
keys.Add(key);
|
||||
values.Add(new Config { Key = key, Value = value });
|
||||
});
|
||||
|
||||
Mocker.GetMock<IConfigRepository>().Setup(c => c.All()).Returns(values);
|
||||
|
||||
foreach (var propertyInfo in allProperties)
|
||||
{
|
||||
@@ -148,8 +106,7 @@ namespace NzbDrone.Core.Test.Configuration
|
||||
returnValue.Should().Be(value, propertyInfo.Name);
|
||||
}
|
||||
|
||||
AllStoredModels.Should()
|
||||
.HaveSameCount(allProperties, "two different properties are writing to the same key in db. Copy/Past fail.");
|
||||
keys.Should().OnlyHaveUniqueItems();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.DecisionEngine.Specifications.RssSync;
|
||||
using NzbDrone.Core.History;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
@@ -63,6 +64,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||
|
||||
_upgradableQuality = new QualityModel(Quality.SDTV, new Revision(version: 1));
|
||||
_notupgradableQuality = new QualityModel(Quality.HDTV1080p, new Revision(version: 2));
|
||||
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.SetupGet(s => s.EnableCompletedDownloadHandling)
|
||||
.Returns(true);
|
||||
}
|
||||
|
||||
private void GivenMostRecentForEpisode(int episodeId, string downloadId, QualityModel quality, DateTime date, HistoryEventType eventType)
|
||||
@@ -71,6 +76,13 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||
.Returns(new History.History { DownloadId = downloadId, Quality = quality, Date = date, EventType = eventType });
|
||||
}
|
||||
|
||||
private void GivenCdhDisabled()
|
||||
{
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.SetupGet(s => s.EnableCompletedDownloadHandling)
|
||||
.Returns(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_if_it_is_a_search()
|
||||
{
|
||||
@@ -91,12 +103,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||
_upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_if_latest_history_has_a_download_id()
|
||||
{
|
||||
GivenMostRecentForEpisode(FIRST_EPISODE_ID, "test", _notupgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed);
|
||||
_upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeTrue();
|
||||
}
|
||||
// [Test]
|
||||
// public void should_return_true_if_latest_history_has_a_download_id_and_cdh_is_enabled()
|
||||
// {
|
||||
// GivenMostRecentForEpisode(FIRST_EPISODE_ID, "test", _notupgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed);
|
||||
// _upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeTrue();
|
||||
// }
|
||||
|
||||
[Test]
|
||||
public void should_return_true_if_latest_history_item_is_older_than_twelve_hours()
|
||||
@@ -174,5 +186,34 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||
GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow.AddHours(-1), HistoryEventType.Grabbed);
|
||||
_upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_false_if_latest_history_has_a_download_id_and_cdh_is_disabled()
|
||||
{
|
||||
GivenCdhDisabled();
|
||||
GivenMostRecentForEpisode(FIRST_EPISODE_ID, "test", _upgradableQuality, DateTime.UtcNow.AddDays(-100), HistoryEventType.Grabbed);
|
||||
_upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_false_if_cutoff_already_met_and_cdh_is_disabled()
|
||||
{
|
||||
GivenCdhDisabled();
|
||||
_fakeSeries.Profile = new Profile { Cutoff = Quality.WEBDL1080p, Items = Qualities.QualityFixture.GetDefaultQualities() };
|
||||
_parseResultSingle.ParsedEpisodeInfo.Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 1));
|
||||
_upgradableQuality = new QualityModel(Quality.WEBDL1080p, new Revision(version: 1));
|
||||
|
||||
GivenMostRecentForEpisode(FIRST_EPISODE_ID, "test", _upgradableQuality, DateTime.UtcNow.AddDays(-100), HistoryEventType.Grabbed);
|
||||
|
||||
_upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_false_if_only_episode_is_not_upgradable_and_cdh_is_disabled()
|
||||
{
|
||||
GivenCdhDisabled();
|
||||
GivenMostRecentForEpisode(FIRST_EPISODE_ID, "test", _notupgradableQuality, DateTime.UtcNow.AddDays(-100), HistoryEventType.Grabbed);
|
||||
_upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,6 +381,30 @@ namespace NzbDrone.Core.Test.Download
|
||||
AssertCompletedDownload();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_warn_if_path_is_not_valid_for_windows()
|
||||
{
|
||||
WindowsOnly();
|
||||
|
||||
_trackedDownload.DownloadItem.OutputPath = new OsPath(@"/invalid/Windows/Path");
|
||||
|
||||
Subject.Process(_trackedDownload);
|
||||
|
||||
AssertNoAttemptedImport();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_warn_if_path_is_not_valid_for_linux()
|
||||
{
|
||||
MonoOnly();
|
||||
|
||||
_trackedDownload.DownloadItem.OutputPath = new OsPath(@"C:\Invalid\Mono\Path");
|
||||
|
||||
Subject.Process(_trackedDownload);
|
||||
|
||||
AssertNoAttemptedImport();
|
||||
}
|
||||
|
||||
private void AssertNoAttemptedImport()
|
||||
{
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
|
||||
@@ -9,7 +9,9 @@ using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Download.Clients.TorrentBlackhole;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.MediaFiles.TorrentInfo;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
@@ -67,6 +69,21 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
.Returns(1000000);
|
||||
}
|
||||
|
||||
protected override RemoteEpisode CreateRemoteEpisode()
|
||||
{
|
||||
var remoteEpisode = base.CreateRemoteEpisode();
|
||||
var torrentInfo = new TorrentInfo();
|
||||
|
||||
torrentInfo.Title = remoteEpisode.Release.Title;
|
||||
torrentInfo.DownloadUrl = remoteEpisode.Release.DownloadUrl;
|
||||
torrentInfo.DownloadProtocol = remoteEpisode.Release.DownloadProtocol;
|
||||
torrentInfo.MagnetUrl = "magnet:?xt=urn:btih:755248817d32b00cc853e633ecdc48e4c21bff15&dn=Series.S05E10.PROPER.HDTV.x264-DEFiNE%5Brartv%5D&tr=http%3A%2F%2Ftracker.trackerfix.com%3A80%2Fannounce&tr=udp%3A%2F%2F9.rarbg.me%3A2710&tr=udp%3A%2F%2F9.rarbg.to%3A2710";
|
||||
|
||||
remoteEpisode.Release = torrentInfo;
|
||||
|
||||
return remoteEpisode;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void completed_download_should_have_required_properties()
|
||||
{
|
||||
@@ -116,6 +133,15 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_throw_if_magnet_and_torrent_url_does_not_exist()
|
||||
{
|
||||
var remoteEpisode = CreateRemoteEpisode();
|
||||
remoteEpisode.Release.DownloadUrl = null;
|
||||
|
||||
Assert.Throws<ReleaseDownloadException>(() => Subject.Download(remoteEpisode));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetItems_should_considered_locked_files_queued()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Download.Clients.Nzbget;
|
||||
using NzbDrone.Test.Common;
|
||||
using NzbDrone.Core.RemotePathMappings;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Download.Clients;
|
||||
using NzbDrone.Core.Download.Clients.NzbVortex;
|
||||
using NzbDrone.Core.Download.Clients.NzbVortex.Responses;
|
||||
|
||||
namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbVortexTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class NzbVortexFixture : DownloadClientFixtureBase<NzbVortex>
|
||||
{
|
||||
private NzbVortexQueueItem _queued;
|
||||
private NzbVortexQueueItem _failed;
|
||||
private NzbVortexQueueItem _completed;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Subject.Definition = new DownloadClientDefinition();
|
||||
Subject.Definition.Settings = new NzbVortexSettings
|
||||
{
|
||||
Host = "127.0.0.1",
|
||||
Port = 2222,
|
||||
ApiKey = "1234-ABCD",
|
||||
TvCategory = "tv",
|
||||
RecentTvPriority = (int)NzbgetPriority.High
|
||||
};
|
||||
|
||||
_queued = new NzbVortexQueueItem
|
||||
{
|
||||
Id = RandomNumber,
|
||||
DownloadedSize = 1000,
|
||||
TotalDownloadSize = 10,
|
||||
GroupName = "tv",
|
||||
UiTitle = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE"
|
||||
};
|
||||
|
||||
_failed = new NzbVortexQueueItem
|
||||
{
|
||||
DownloadedSize = 1000,
|
||||
TotalDownloadSize = 1000,
|
||||
GroupName = "tv",
|
||||
UiTitle = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE",
|
||||
DestinationPath = "somedirectory",
|
||||
State = NzbVortexStateType.UncompressFailed,
|
||||
};
|
||||
|
||||
_completed = new NzbVortexQueueItem
|
||||
{
|
||||
DownloadedSize = 1000,
|
||||
TotalDownloadSize = 1000,
|
||||
GroupName = "tv",
|
||||
UiTitle = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE",
|
||||
DestinationPath = "/remote/mount/tv/Droned.S01E01.Pilot.1080p.WEB-DL-DRONE",
|
||||
State = NzbVortexStateType.Done
|
||||
};
|
||||
}
|
||||
|
||||
protected void GivenFailedDownload()
|
||||
{
|
||||
Mocker.GetMock<INzbVortexProxy>()
|
||||
.Setup(s => s.DownloadNzb(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<NzbVortexSettings>()))
|
||||
.Returns((string)null);
|
||||
}
|
||||
|
||||
protected void GivenSuccessfulDownload()
|
||||
{
|
||||
Mocker.GetMock<INzbVortexProxy>()
|
||||
.Setup(s => s.DownloadNzb(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<NzbVortexSettings>()))
|
||||
.Returns(Guid.NewGuid().ToString().Replace("-", ""));
|
||||
}
|
||||
|
||||
protected virtual void GivenQueue(NzbVortexQueueItem queue)
|
||||
{
|
||||
var list = new List<NzbVortexQueueItem>();
|
||||
|
||||
list.AddIfNotNull(queue);
|
||||
|
||||
Mocker.GetMock<INzbVortexProxy>()
|
||||
.Setup(s => s.GetQueue(It.IsAny<int>(), It.IsAny<NzbVortexSettings>()))
|
||||
.Returns(new NzbVortexQueue
|
||||
{
|
||||
Items = list
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetItems_should_return_no_items_when_queue_is_empty()
|
||||
{
|
||||
GivenQueue(null);
|
||||
|
||||
Subject.GetItems().Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void queued_item_should_have_required_properties()
|
||||
{
|
||||
GivenQueue(_queued);
|
||||
|
||||
var result = Subject.GetItems().Single();
|
||||
|
||||
VerifyQueued(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void paused_item_should_have_required_properties()
|
||||
{
|
||||
_queued.IsPaused = true;
|
||||
GivenQueue(_queued);
|
||||
|
||||
var result = Subject.GetItems().Single();
|
||||
|
||||
VerifyPaused(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void downloading_item_should_have_required_properties()
|
||||
{
|
||||
_queued.State = NzbVortexStateType.Downloading;
|
||||
GivenQueue(_queued);
|
||||
|
||||
var result = Subject.GetItems().Single();
|
||||
|
||||
VerifyDownloading(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void completed_download_should_have_required_properties()
|
||||
{
|
||||
GivenQueue(_completed);
|
||||
|
||||
var result = Subject.GetItems().Single();
|
||||
|
||||
VerifyCompleted(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void failed_item_should_have_required_properties()
|
||||
{
|
||||
GivenQueue(_failed);
|
||||
|
||||
var result = Subject.GetItems().Single();
|
||||
|
||||
VerifyFailed(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_report_UncompressFailed_as_failed()
|
||||
{
|
||||
_queued.State = NzbVortexStateType.UncompressFailed;
|
||||
GivenQueue(_failed);
|
||||
|
||||
var items = Subject.GetItems();
|
||||
|
||||
items.First().Status.Should().Be(DownloadItemStatus.Failed);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_report_CheckFailedDataCorrupt_as_failed()
|
||||
{
|
||||
_queued.State = NzbVortexStateType.CheckFailedDataCorrupt;
|
||||
GivenQueue(_failed);
|
||||
|
||||
var result = Subject.GetItems().Single();
|
||||
|
||||
result.Status.Should().Be(DownloadItemStatus.Failed);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_report_BadlyEncoded_as_failed()
|
||||
{
|
||||
_queued.State = NzbVortexStateType.BadlyEncoded;
|
||||
GivenQueue(_failed);
|
||||
|
||||
var items = Subject.GetItems();
|
||||
|
||||
items.First().Status.Should().Be(DownloadItemStatus.Failed);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_return_unique_id()
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteEpisode = CreateRemoteEpisode();
|
||||
|
||||
var id = Subject.Download(remoteEpisode);
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_throw_if_failed()
|
||||
{
|
||||
GivenFailedDownload();
|
||||
|
||||
var remoteEpisode = CreateRemoteEpisode();
|
||||
|
||||
Assert.Throws<DownloadClientException>(() => Subject.Download(remoteEpisode));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetItems_should_ignore_downloads_from_other_categories()
|
||||
{
|
||||
_completed.GroupName = "mycat";
|
||||
|
||||
GivenQueue(null);
|
||||
|
||||
var items = Subject.GetItems();
|
||||
|
||||
items.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_remap_storage_if_mounted()
|
||||
{
|
||||
Mocker.GetMock<IRemotePathMappingService>()
|
||||
.Setup(v => v.RemapRemoteToLocal("127.0.0.1", It.IsAny<OsPath>()))
|
||||
.Returns(new OsPath(@"O:\mymount\Droned.S01E01.Pilot.1080p.WEB-DL-DRONE".AsOsAgnostic()));
|
||||
|
||||
GivenQueue(_completed);
|
||||
|
||||
var result = Subject.GetItems().Single();
|
||||
|
||||
result.OutputPath.Should().Be(@"O:\mymount\Droned.S01E01.Pilot.1080p.WEB-DL-DRONE".AsOsAgnostic());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_get_files_if_completed_download_is_not_in_a_job_folder()
|
||||
{
|
||||
Mocker.GetMock<IRemotePathMappingService>()
|
||||
.Setup(v => v.RemapRemoteToLocal("127.0.0.1", It.IsAny<OsPath>()))
|
||||
.Returns(new OsPath(@"O:\mymount\".AsOsAgnostic()));
|
||||
|
||||
Mocker.GetMock<INzbVortexProxy>()
|
||||
.Setup(s => s.GetFiles(It.IsAny<int>(), It.IsAny<NzbVortexSettings>()))
|
||||
.Returns(new NzbVortexFiles{ Files = new List<NzbVortexFile> { new NzbVortexFile { FileName = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE.mkv" } } });
|
||||
|
||||
_completed.State = NzbVortexStateType.Done;
|
||||
GivenQueue(_completed);
|
||||
|
||||
var result = Subject.GetItems().Single();
|
||||
|
||||
result.OutputPath.Should().Be(@"O:\mymount\Droned.S01E01.Pilot.1080p.WEB-DL-DRONE.mkv".AsOsAgnostic());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_warning_if_more_than_one_file_is_not_in_a_job_folder()
|
||||
{
|
||||
Mocker.GetMock<IRemotePathMappingService>()
|
||||
.Setup(v => v.RemapRemoteToLocal("127.0.0.1", It.IsAny<OsPath>()))
|
||||
.Returns(new OsPath(@"O:\mymount\".AsOsAgnostic()));
|
||||
|
||||
Mocker.GetMock<INzbVortexProxy>()
|
||||
.Setup(s => s.GetFiles(It.IsAny<int>(), It.IsAny<NzbVortexSettings>()))
|
||||
.Returns(new NzbVortexFiles { Files = new List<NzbVortexFile>
|
||||
{
|
||||
new NzbVortexFile { FileName = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE.mkv" },
|
||||
new NzbVortexFile { FileName = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE.nfo" }
|
||||
} });
|
||||
|
||||
_completed.State = NzbVortexStateType.Done;
|
||||
GivenQueue(_completed);
|
||||
|
||||
var result = Subject.GetItems().Single();
|
||||
|
||||
result.Status.Should().Be(DownloadItemStatus.Warning);
|
||||
}
|
||||
|
||||
[TestCase("1.0", false)]
|
||||
[TestCase("2.2", false)]
|
||||
[TestCase("2.3", true)]
|
||||
[TestCase("2.4", true)]
|
||||
[TestCase("3.0", true)]
|
||||
public void should_test_api_version(string version, bool expected)
|
||||
{
|
||||
Mocker.GetMock<INzbVortexProxy>()
|
||||
.Setup(v => v.GetGroups(It.IsAny<NzbVortexSettings>()))
|
||||
.Returns(new List<NzbVortexGroup> { new NzbVortexGroup { GroupName = ((NzbVortexSettings)Subject.Definition.Settings).TvCategory } });
|
||||
|
||||
Mocker.GetMock<INzbVortexProxy>()
|
||||
.Setup(v => v.GetApiVersion(It.IsAny<NzbVortexSettings>()))
|
||||
.Returns(new NzbVortexApiVersionResponse { ApiLevel = version });
|
||||
|
||||
var error = Subject.Test();
|
||||
|
||||
error.IsValid.Should().Be(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.MediaFiles.TorrentInfo;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Download.Clients.QBittorrent;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class QBittorrentFixture : DownloadClientFixtureBase<QBittorrent>
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Subject.Definition = new DownloadClientDefinition();
|
||||
Subject.Definition.Settings = new QBittorrentSettings
|
||||
{
|
||||
Host = "127.0.0.1",
|
||||
Port = 2222,
|
||||
Username = "admin",
|
||||
Password = "pass",
|
||||
TvCategory = "tv"
|
||||
};
|
||||
|
||||
Mocker.GetMock<ITorrentFileInfoReader>()
|
||||
.Setup(s => s.GetHashFromTorrentFile(It.IsAny<Byte[]>()))
|
||||
.Returns("CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new Byte[0]));
|
||||
}
|
||||
|
||||
protected void GivenRedirectToMagnet()
|
||||
{
|
||||
var httpHeader = new HttpHeader();
|
||||
httpHeader["Location"] = "magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp";
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, httpHeader, new Byte[0], System.Net.HttpStatusCode.SeeOther));
|
||||
}
|
||||
|
||||
protected void GivenRedirectToTorrent()
|
||||
{
|
||||
var httpHeader = new HttpHeader();
|
||||
httpHeader["Location"] = "http://test.sonarr.tv/not-a-real-torrent.torrent";
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(s => s.Get(It.Is<HttpRequest>(h => h.Url.AbsoluteUri == _downloadUrl)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, httpHeader, new Byte[0], System.Net.HttpStatusCode.Found));
|
||||
}
|
||||
|
||||
protected void GivenFailedDownload()
|
||||
{
|
||||
Mocker.GetMock<IQBittorrentProxy>()
|
||||
.Setup(s => s.AddTorrentFromUrl(It.IsAny<string>(), It.IsAny<QBittorrentSettings>()))
|
||||
.Throws<InvalidOperationException>();
|
||||
}
|
||||
|
||||
protected void GivenSuccessfulDownload()
|
||||
{
|
||||
Mocker.GetMock<IQBittorrentProxy>()
|
||||
.Setup(s => s.AddTorrentFromUrl(It.IsAny<string>(), It.IsAny<QBittorrentSettings>()))
|
||||
.Callback(() =>
|
||||
{
|
||||
var torrent = new QBittorrentTorrent
|
||||
{
|
||||
Hash = "HASH",
|
||||
Name = _title,
|
||||
Size = 1000,
|
||||
Progress = 1.0,
|
||||
Eta = 8640000,
|
||||
State = "queuedUP",
|
||||
Label = "",
|
||||
SavePath = ""
|
||||
};
|
||||
GivenTorrents(new List<QBittorrentTorrent> { torrent });
|
||||
});
|
||||
}
|
||||
|
||||
protected virtual void GivenTorrents(List<QBittorrentTorrent> torrents)
|
||||
{
|
||||
if (torrents == null)
|
||||
torrents = new List<QBittorrentTorrent>();
|
||||
|
||||
Mocker.GetMock<IQBittorrentProxy>()
|
||||
.Setup(s => s.GetTorrents(It.IsAny<QBittorrentSettings>()))
|
||||
.Returns(torrents);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void error_item_should_have_required_properties()
|
||||
{
|
||||
var torrent = new QBittorrentTorrent
|
||||
{
|
||||
Hash = "HASH",
|
||||
Name = _title,
|
||||
Size = 1000,
|
||||
Progress = 0.7,
|
||||
Eta = 8640000,
|
||||
State = "error",
|
||||
Label = "",
|
||||
SavePath = ""
|
||||
};
|
||||
GivenTorrents(new List<QBittorrentTorrent> { torrent });
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
VerifyFailed(item);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void paused_item_should_have_required_properties()
|
||||
{
|
||||
var torrent = new QBittorrentTorrent
|
||||
{
|
||||
Hash = "HASH",
|
||||
Name = _title,
|
||||
Size = 1000,
|
||||
Progress = 0.7,
|
||||
Eta = 8640000,
|
||||
State = "pausedDL",
|
||||
Label = "",
|
||||
SavePath = ""
|
||||
};
|
||||
GivenTorrents(new List<QBittorrentTorrent> { torrent });
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
VerifyPaused(item);
|
||||
item.RemainingTime.Should().NotBe(TimeSpan.Zero);
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("queuedUP")]
|
||||
[TestCase("uploading")]
|
||||
[TestCase("stalledUP")]
|
||||
[TestCase("checkingUP")]
|
||||
public void completed_item_should_have_required_properties(string state)
|
||||
{
|
||||
var torrent = new QBittorrentTorrent
|
||||
{
|
||||
Hash = "HASH",
|
||||
Name = _title,
|
||||
Size = 1000,
|
||||
Progress = 1.0,
|
||||
Eta = 8640000,
|
||||
State = state,
|
||||
Label = "",
|
||||
SavePath = ""
|
||||
};
|
||||
GivenTorrents(new List<QBittorrentTorrent> { torrent });
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
VerifyCompleted(item);
|
||||
item.RemainingTime.Should().Be(TimeSpan.Zero);
|
||||
}
|
||||
|
||||
[TestCase("queuedDL")]
|
||||
[TestCase("checkingDL")]
|
||||
public void queued_item_should_have_required_properties(string state)
|
||||
{
|
||||
var torrent = new QBittorrentTorrent
|
||||
{
|
||||
Hash = "HASH",
|
||||
Name = _title,
|
||||
Size = 1000,
|
||||
Progress = 0.7,
|
||||
Eta = 8640000,
|
||||
State = state,
|
||||
Label = "",
|
||||
SavePath = ""
|
||||
};
|
||||
GivenTorrents(new List<QBittorrentTorrent> { torrent });
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
VerifyQueued(item);
|
||||
item.RemainingTime.Should().NotBe(TimeSpan.Zero);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void downloading_item_should_have_required_properties()
|
||||
{
|
||||
var torrent = new QBittorrentTorrent
|
||||
{
|
||||
Hash = "HASH",
|
||||
Name = _title,
|
||||
Size = 1000,
|
||||
Progress = 0.7,
|
||||
Eta = 60,
|
||||
State = "downloading",
|
||||
Label = "",
|
||||
SavePath = ""
|
||||
};
|
||||
GivenTorrents(new List<QBittorrentTorrent> { torrent });
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
VerifyDownloading(item);
|
||||
item.RemainingTime.Should().NotBe(TimeSpan.Zero);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void stalledDL_item_should_have_required_properties()
|
||||
{
|
||||
var torrent = new QBittorrentTorrent
|
||||
{
|
||||
Hash = "HASH",
|
||||
Name = _title,
|
||||
Size = 1000,
|
||||
Progress = 0.7,
|
||||
Eta = 8640000,
|
||||
State = "stalledDL",
|
||||
Label = "",
|
||||
SavePath = ""
|
||||
};
|
||||
GivenTorrents(new List<QBittorrentTorrent> { torrent });
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
VerifyWarning(item);
|
||||
item.RemainingTime.Should().NotBe(TimeSpan.Zero);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_return_unique_id()
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteEpisode = CreateRemoteEpisode();
|
||||
|
||||
var id = Subject.Download(remoteEpisode);
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")]
|
||||
public void Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteEpisode = CreateRemoteEpisode();
|
||||
remoteEpisode.Release.DownloadUrl = magnetUrl;
|
||||
|
||||
var id = Subject.Download(remoteEpisode);
|
||||
|
||||
id.Should().Be(expectedHash);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_status_with_outputdirs()
|
||||
{
|
||||
var configItems = new Dictionary<string, Object>();
|
||||
|
||||
configItems.Add("save_path", @"C:\Downloads\Finished\QBittorrent".AsOsAgnostic());
|
||||
|
||||
Mocker.GetMock<IQBittorrentProxy>()
|
||||
.Setup(v => v.GetConfig(It.IsAny<QBittorrentSettings>()))
|
||||
.Returns(configItems);
|
||||
|
||||
var result = Subject.GetStatus();
|
||||
|
||||
result.IsLocalhost.Should().BeTrue();
|
||||
result.OutputRootFolders.Should().NotBeNull();
|
||||
result.OutputRootFolders.First().Should().Be(@"C:\Downloads\Finished\QBittorrent".AsOsAgnostic());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_handle_http_redirect_to_magnet()
|
||||
{
|
||||
GivenRedirectToMagnet();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteEpisode = CreateRemoteEpisode();
|
||||
|
||||
var id = Subject.Download(remoteEpisode);
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_handle_http_redirect_to_torrent()
|
||||
{
|
||||
GivenRedirectToTorrent();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteEpisode = CreateRemoteEpisode();
|
||||
|
||||
var id = Subject.Download(remoteEpisode);
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,7 +144,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
history = new SabnzbdHistory() { Items = new List<SabnzbdHistoryItem>() };
|
||||
|
||||
Mocker.GetMock<ISabnzbdProxy>()
|
||||
.Setup(s => s.GetHistory(It.IsAny<int>(), It.IsAny<int>(), It.IsAny<SabnzbdSettings>()))
|
||||
.Setup(s => s.GetHistory(It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<SabnzbdSettings>()))
|
||||
.Returns(history);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
||||
protected TransmissionTorrent _downloading;
|
||||
protected TransmissionTorrent _failed;
|
||||
protected TransmissionTorrent _completed;
|
||||
protected TransmissionTorrent _magnet;
|
||||
protected Dictionary<string, object> _transmissionConfigItems;
|
||||
|
||||
[SetUp]
|
||||
@@ -80,6 +81,17 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
||||
DownloadDir = "somepath"
|
||||
};
|
||||
|
||||
_magnet = new TransmissionTorrent
|
||||
{
|
||||
HashString = "HASH",
|
||||
IsFinished = false,
|
||||
Status = TransmissionTorrentStatus.Downloading,
|
||||
Name = _title,
|
||||
TotalSize = 0,
|
||||
LeftUntilDone = 100,
|
||||
DownloadDir = "somepath"
|
||||
};
|
||||
|
||||
Mocker.GetMock<ITorrentFileInfoReader>()
|
||||
.Setup(s => s.GetHashFromTorrentFile(It.IsAny<byte[]>()))
|
||||
.Returns("CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951");
|
||||
@@ -105,6 +117,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
||||
_settings.TvCategory = "sonarr";
|
||||
}
|
||||
|
||||
protected void GivenTvDirectory()
|
||||
{
|
||||
_settings.TvDirectory = @"C:/Downloads/Finished/sonarr";
|
||||
}
|
||||
|
||||
protected void GivenFailedDownload()
|
||||
{
|
||||
Mocker.GetMock<ITransmissionProxy>()
|
||||
@@ -171,6 +188,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
||||
});
|
||||
}
|
||||
|
||||
protected void PrepareClientToReturnMagnetItem()
|
||||
{
|
||||
GivenTorrents(new List<TransmissionTorrent>
|
||||
{
|
||||
_magnet
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void queued_item_should_have_required_properties()
|
||||
{
|
||||
@@ -203,6 +228,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
||||
VerifyCompleted(item);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void magnet_download_should_not_return_the_item()
|
||||
{
|
||||
PrepareClientToReturnMagnetItem();
|
||||
Subject.GetItems().Count().Should().Be(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_return_unique_id()
|
||||
{
|
||||
@@ -215,6 +247,22 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_with_TvDirectory_should_force_directory()
|
||||
{
|
||||
GivenTvDirectory();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteEpisode = CreateRemoteEpisode();
|
||||
|
||||
var id = Subject.Download(remoteEpisode);
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
Mocker.GetMock<ITransmissionProxy>()
|
||||
.Verify(v => v.AddTorrentFromData(It.IsAny<byte[]>(), @"C:/Downloads/Finished/sonarr", It.IsAny<TransmissionSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_with_category_should_force_directory()
|
||||
{
|
||||
@@ -249,6 +297,21 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
||||
.Verify(v => v.AddTorrentFromData(It.IsAny<byte[]>(), @"C:/Downloads/Finished/transmission/sonarr", It.IsAny<TransmissionSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_without_TvDirectory_and_Category_should_use_default()
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteEpisode = CreateRemoteEpisode();
|
||||
|
||||
var id = Subject.Download(remoteEpisode);
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
Mocker.GetMock<ITransmissionProxy>()
|
||||
.Verify(v => v.AddTorrentFromData(It.IsAny<byte[]>(), null, It.IsAny<TransmissionSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
[TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")]
|
||||
public void Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
||||
{
|
||||
@@ -341,6 +404,24 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
||||
items.First().Status.Should().Be(DownloadItemStatus.Downloading);
|
||||
}
|
||||
|
||||
public void should_exclude_items_not_in_TvDirectory()
|
||||
{
|
||||
GivenTvDirectory();
|
||||
|
||||
_downloading.DownloadDir = @"C:/Downloads/Finished/sonarr/subdir";
|
||||
|
||||
GivenTorrents(new List<TransmissionTorrent>
|
||||
{
|
||||
_downloading,
|
||||
_queued
|
||||
});
|
||||
|
||||
var items = Subject.GetItems().ToList();
|
||||
|
||||
items.Count.Should().Be(1);
|
||||
items.First().Status.Should().Be(DownloadItemStatus.Downloading);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_fix_forward_slashes()
|
||||
{
|
||||
|
||||
@@ -143,7 +143,28 @@ namespace NzbDrone.Core.Test.Download
|
||||
Assert.Throws<ReleaseDownloadException>(() => Subject.DownloadReport(_parseResult));
|
||||
|
||||
Mocker.GetMock<IIndexerStatusService>()
|
||||
.Verify(v => v.RecordFailure(It.IsAny<int>(), TimeSpan.FromMinutes(5)), Times.Once());
|
||||
.Verify(v => v.RecordFailure(It.IsAny<int>(), TimeSpan.FromMinutes(5.0)), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_report_should_trigger_indexer_backoff_on_http429_based_on_date()
|
||||
{
|
||||
var request = new HttpRequest("http://my.indexer.com");
|
||||
var response = new HttpResponse(request, new HttpHeader(), new byte[0], (HttpStatusCode)429);
|
||||
response.Headers["Retry-After"] = DateTime.UtcNow.AddSeconds(300).ToString("r");
|
||||
|
||||
var mock = WithUsenetClient();
|
||||
mock.Setup(s => s.Download(It.IsAny<RemoteEpisode>()))
|
||||
.Callback<RemoteEpisode>(v =>
|
||||
{
|
||||
throw new ReleaseDownloadException(v.Release, "Error", new TooManyRequestsException(request, response));
|
||||
});
|
||||
|
||||
Assert.Throws<ReleaseDownloadException>(() => Subject.DownloadReport(_parseResult));
|
||||
|
||||
Mocker.GetMock<IIndexerStatusService>()
|
||||
.Verify(v => v.RecordFailure(It.IsAny<int>(),
|
||||
It.IsInRange<TimeSpan>(TimeSpan.FromMinutes(4.9), TimeSpan.FromMinutes(5.1), Range.Inclusive)), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -48,8 +48,8 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
|
||||
};
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(s => s.Map(It.Is<ParsedEpisodeInfo>(i => i.SeasonNumber == 1 && i.SeriesTitle == "TV Series"), It.IsAny<int>(), It.IsAny<IEnumerable<int>>()))
|
||||
.Returns(remoteEpisode);
|
||||
.Setup(s => s.Map(It.Is<ParsedEpisodeInfo>(i => i.SeasonNumber == 1 && i.SeriesTitle == "TV Series"), It.IsAny<int>(), It.IsAny<IEnumerable<int>>()))
|
||||
.Returns(remoteEpisode);
|
||||
|
||||
var client = new DownloadClientDefinition()
|
||||
{
|
||||
@@ -72,5 +72,61 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
|
||||
trackedDownload.RemoteEpisode.Episodes.First().Id.Should().Be(4);
|
||||
trackedDownload.RemoteEpisode.ParsedEpisodeInfo.SeasonNumber.Should().Be(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_as_special_when_source_title_parsing_fails()
|
||||
{
|
||||
var remoteEpisode = new RemoteEpisode
|
||||
{
|
||||
Series = new Series() { Id = 5 },
|
||||
Episodes = new List<Episode> { new Episode { Id = 4 } },
|
||||
ParsedEpisodeInfo = new ParsedEpisodeInfo()
|
||||
{
|
||||
SeriesTitle = "TV Series",
|
||||
SeasonNumber = 0,
|
||||
EpisodeNumbers = new []{ 1 }
|
||||
}
|
||||
};
|
||||
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Setup(s => s.FindByDownloadId(It.Is<string>(sr => sr == "35238")))
|
||||
.Returns(new List<History.History>(){
|
||||
new History.History(){
|
||||
DownloadId = "35238",
|
||||
SourceTitle = "TV Series Special",
|
||||
SeriesId = 5,
|
||||
EpisodeId = 4
|
||||
}
|
||||
});
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(s => s.Map(It.Is<ParsedEpisodeInfo>(i => i.SeasonNumber == 0 && i.SeriesTitle == "TV Series"), It.IsAny<int>(), It.IsAny<IEnumerable<int>>()))
|
||||
.Returns(remoteEpisode);
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(s => s.ParseSpecialEpisodeTitle(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>(), null))
|
||||
.Returns(remoteEpisode.ParsedEpisodeInfo);
|
||||
|
||||
var client = new DownloadClientDefinition()
|
||||
{
|
||||
Id = 1,
|
||||
Protocol = DownloadProtocol.Torrent
|
||||
};
|
||||
|
||||
var item = new DownloadClientItem()
|
||||
{
|
||||
Title = "The torrent release folder",
|
||||
DownloadId = "35238",
|
||||
};
|
||||
|
||||
var trackedDownload = Subject.TrackDownload(client, item);
|
||||
|
||||
trackedDownload.Should().NotBeNull();
|
||||
trackedDownload.RemoteEpisode.Should().NotBeNull();
|
||||
trackedDownload.RemoteEpisode.Series.Should().NotBeNull();
|
||||
trackedDownload.RemoteEpisode.Series.Id.Should().Be(5);
|
||||
trackedDownload.RemoteEpisode.Episodes.First().Id.Should().Be(4);
|
||||
trackedDownload.RemoteEpisode.ParsedEpisodeInfo.SeasonNumber.Should().Be(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
{
|
||||
"code": "SUCCESSFUL",
|
||||
"http_code": 200,
|
||||
"limit": "2",
|
||||
"offset": 0,
|
||||
"results": [
|
||||
{
|
||||
"air_date": "20150623",
|
||||
"anonymous": 1,
|
||||
"codec": "x264",
|
||||
"container": "MKV",
|
||||
"created_at": "2015-06-25 04:13:44",
|
||||
"download": "https://titansof.tv/api/torrents/19445/download?apikey=abc",
|
||||
"ecommentUrl": "https://titansof.tv/series/287053/episode/5453241#comments",
|
||||
"episode": "S02E04",
|
||||
"episodeUrl": "https://titansof.tv/series/287053/episode/5453241",
|
||||
"episode_id": "5453241",
|
||||
"id": "19445",
|
||||
"language": "en",
|
||||
"leechers": 5,
|
||||
"network": "truTV",
|
||||
"origin": "Scene",
|
||||
"release_name": "Series.Title.S02E04.720p.HDTV.x264-W4F",
|
||||
"resolution": "720p",
|
||||
"season": "",
|
||||
"season_id": 0,
|
||||
"seeders": 2,
|
||||
"series": "Series Title",
|
||||
"series_id": "287053",
|
||||
"size": 435402993,
|
||||
"snatched": 0,
|
||||
"source": "HDTV",
|
||||
"updated_at": "2015-06-25 04:13:44",
|
||||
"user_id": 0
|
||||
},
|
||||
{
|
||||
"air_date": "20150624",
|
||||
"anonymous": 1,
|
||||
"codec": "x264",
|
||||
"container": "MKV",
|
||||
"created_at": "2015-06-25 04:11:59",
|
||||
"download": "https://titansof.tv/api/torrents/19444/download?apikey=abc",
|
||||
"ecommentUrl": "https://titansof.tv/series/75382/episode/5443517#comments",
|
||||
"episode": "S21E10",
|
||||
"episodeUrl": "https://titansof.tv/series/75382/episode/5443517",
|
||||
"episode_id": "5443517",
|
||||
"id": "19444",
|
||||
"language": "en",
|
||||
"leechers": 0,
|
||||
"network": "FX",
|
||||
"origin": "User",
|
||||
"release_name": "Series.Title.S21E10.720p.HDTV.x264-KOENiG",
|
||||
"resolution": "720p",
|
||||
"season": "",
|
||||
"season_id": 0,
|
||||
"seeders": 1,
|
||||
"series": "Series Title",
|
||||
"series_id": "75382",
|
||||
"size": 949968933,
|
||||
"snatched": 0,
|
||||
"source": "HDTV",
|
||||
"updated_at": "2015-06-25 04:11:59",
|
||||
"user_id": 0
|
||||
}
|
||||
],
|
||||
"total": 18546
|
||||
}
|
||||
@@ -3,7 +3,9 @@ using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.HealthCheck.Checks;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
@@ -25,6 +27,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
public void should_return_error_when_download_client_throws()
|
||||
{
|
||||
var downloadClient = Mocker.GetMock<IDownloadClient>();
|
||||
downloadClient.Setup(s => s.Definition).Returns(new IndexerDefinition{Name = "Test"});
|
||||
|
||||
downloadClient.Setup(s => s.GetItems())
|
||||
.Throws<Exception>();
|
||||
|
||||
@@ -6,6 +6,7 @@ using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.HealthCheck.Checks;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Update;
|
||||
|
||||
namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
{
|
||||
@@ -21,8 +22,8 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
.Setup(s => s.StartUpFolder)
|
||||
.Returns(@"C:\NzbDrone");
|
||||
|
||||
Mocker.GetMock<NzbDrone.Common.Disk.IDiskProvider>()
|
||||
.Setup(c => c.FolderWritable(Moq.It.IsAny<string>()))
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(c => c.FolderWritable(It.IsAny<string>()))
|
||||
.Returns(false);
|
||||
|
||||
Subject.Check().ShouldBeError();
|
||||
@@ -33,19 +34,71 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
{
|
||||
MonoOnly();
|
||||
|
||||
const string startupFolder = @"/opt/nzbdrone";
|
||||
|
||||
Mocker.GetMock<IConfigFileProvider>()
|
||||
.Setup(s => s.UpdateAutomatically)
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IAppFolderInfo>()
|
||||
.Setup(s => s.StartUpFolder)
|
||||
.Returns(@"/opt/nzbdrone");
|
||||
.Returns(startupFolder);
|
||||
|
||||
Mocker.GetMock<NzbDrone.Common.Disk.IDiskProvider>()
|
||||
.Setup(c => c.FolderWritable(Moq.It.IsAny<string>()))
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(c => c.FolderWritable(startupFolder))
|
||||
.Returns(false);
|
||||
|
||||
Subject.Check().ShouldBeError();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_error_when_ui_folder_is_write_protected_and_update_automatically_is_enabled()
|
||||
{
|
||||
MonoOnly();
|
||||
|
||||
const string startupFolder = @"/opt/nzbdrone";
|
||||
const string uiFolder = @"/opt/nzbdrone/UI";
|
||||
|
||||
Mocker.GetMock<IConfigFileProvider>()
|
||||
.Setup(s => s.UpdateAutomatically)
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IAppFolderInfo>()
|
||||
.Setup(s => s.StartUpFolder)
|
||||
.Returns(startupFolder);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(c => c.FolderWritable(startupFolder))
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(c => c.FolderWritable(uiFolder))
|
||||
.Returns(false);
|
||||
|
||||
Subject.Check().ShouldBeError();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_return_error_when_app_folder_is_write_protected_and_external_script_enabled()
|
||||
{
|
||||
MonoOnly();
|
||||
|
||||
Mocker.GetMock<IConfigFileProvider>()
|
||||
.Setup(s => s.UpdateAutomatically)
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IConfigFileProvider>()
|
||||
.Setup(s => s.UpdateMechanism)
|
||||
.Returns(UpdateMechanism.Script);
|
||||
|
||||
Mocker.GetMock<IAppFolderInfo>()
|
||||
.Setup(s => s.StartUpFolder)
|
||||
.Returns(@"/opt/nzbdrone");
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(c => c.FolderWritable(It.IsAny<string>()), Times.Never());
|
||||
|
||||
Subject.Check().ShouldBeOk();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,16 @@ namespace NzbDrone.Core.Test.Http
|
||||
newRequest.Url.AbsoluteUri.Should().Be("http://torcache.net/download/123.torrent");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_add_referrer_torcache_request()
|
||||
{
|
||||
var request = new HttpRequest("http://torcache.net/download/123.torrent?title=something");
|
||||
|
||||
var newRequest = Subject.PreRequest(request);
|
||||
|
||||
newRequest.Headers.Should().Contain("Referer", "http://torcache.net/");
|
||||
}
|
||||
|
||||
[TestCase("http://site.com/download?url=torcache.net&blaat=1")]
|
||||
[TestCase("http://torcache.net.com/download?url=123")]
|
||||
public void should_not_remove_query_params_from_other_requests(string url)
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
[TestCase("Hawaii Five-0", Result = "Hawaii+Five+0")]
|
||||
[TestCase("Franklin & Bash", Result = "Franklin+and+Bash")]
|
||||
[TestCase("Chicago P.D.", Result = "Chicago+PD")]
|
||||
[TestCase("Kourtney And Khloé Take The Hamptons", Result = "Kourtney+And+Khloe+Take+The+Hamptons")]
|
||||
[TestCase("Kourtney And Khlo\u00E9 Take The Hamptons", Result = "Kourtney+And+Khloe+Take+The+Hamptons")]
|
||||
public string should_replace_some_special_characters(string input)
|
||||
{
|
||||
Subject.SceneTitles = new List<string> { input };
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.TitansOfTv;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerTests.TitansOfTvTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TitansOfTvFixture : CoreTest<TitansOfTv>
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Subject.Definition = new IndexerDefinition
|
||||
{
|
||||
Name = "TitansOfTV",
|
||||
Settings = new TitansOfTvSettings { ApiKey = "abc", BaseUrl = "https://titansof.tv/api" }
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_TitansOfTv()
|
||||
{
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/TitansOfTv/RecentFeed.json");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.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();
|
||||
|
||||
releases.Should().HaveCount(2);
|
||||
releases.First().Should().BeOfType<TorrentInfo>();
|
||||
|
||||
var torrentInfo = releases.First() as TorrentInfo;
|
||||
|
||||
torrentInfo.Guid.Should().Be("ToTV-19445");
|
||||
torrentInfo.Title.Should().Be("Series.Title.S02E04.720p.HDTV.x264-W4F");
|
||||
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
|
||||
torrentInfo.DownloadUrl.Should().Be("https://titansof.tv/api/torrents/19445/download?apikey=abc");
|
||||
torrentInfo.InfoUrl.Should().Be("https://titansof.tv/series/287053/episode/5453241");
|
||||
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
|
||||
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
|
||||
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2015-06-25 04:13:44"));
|
||||
torrentInfo.Size.Should().Be(435402993);
|
||||
torrentInfo.InfoHash.Should().BeNullOrEmpty();
|
||||
torrentInfo.TvdbId.Should().Be(0);
|
||||
torrentInfo.TvRageId.Should().Be(0);
|
||||
torrentInfo.MagnetUrl.Should().BeNullOrEmpty();
|
||||
torrentInfo.Peers.Should().Be(2+5);
|
||||
torrentInfo.Seeders.Should().Be(2);
|
||||
}
|
||||
|
||||
private void VerifyBackOff()
|
||||
{
|
||||
Mocker.GetMock<IIndexerStatusService>()
|
||||
.Verify(v => v.RecordFailure(It.IsAny<int>(), It.IsAny<TimeSpan>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_back_off_on_bad_request()
|
||||
{
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(v => v.Execute(It.IsAny<HttpRequest>()))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[0], System.Net.HttpStatusCode.BadRequest));
|
||||
|
||||
var results = Subject.FetchRecent();
|
||||
|
||||
results.Should().BeEmpty();
|
||||
|
||||
VerifyBackOff();
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_back_off_and_report_api_key_invalid()
|
||||
{
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(v => v.Execute(It.IsAny<HttpRequest>()))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[0], System.Net.HttpStatusCode.Unauthorized));
|
||||
|
||||
var results = Subject.FetchRecent();
|
||||
|
||||
results.Should().BeEmpty();
|
||||
|
||||
results.Should().BeEmpty();
|
||||
|
||||
VerifyBackOff();
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_back_off_on_unknown_method()
|
||||
{
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(v => v.Execute(It.IsAny<HttpRequest>()))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[0], System.Net.HttpStatusCode.NotFound));
|
||||
|
||||
var results = Subject.FetchRecent();
|
||||
|
||||
results.Should().BeEmpty();
|
||||
|
||||
VerifyBackOff();
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_back_off_api_limit_reached()
|
||||
{
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(v => v.Execute(It.IsAny<HttpRequest>()))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[0], System.Net.HttpStatusCode.ServiceUnavailable));
|
||||
|
||||
var results = Subject.FetchRecent();
|
||||
|
||||
results.Should().BeEmpty();
|
||||
|
||||
VerifyBackOff();
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_replace_https_http_as_needed()
|
||||
{
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/TitansOfTv/RecentFeed.json");
|
||||
|
||||
(Subject.Definition.Settings as TitansOfTvSettings).BaseUrl = "http://titansof.tv/api/torrents";
|
||||
|
||||
recentFeed = recentFeed.Replace("http:", "https:");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.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();
|
||||
|
||||
releases.Should().HaveCount(2);
|
||||
releases.First().Should().BeOfType<TorrentInfo>();
|
||||
|
||||
var torrentInfo = releases.First() as TorrentInfo;
|
||||
|
||||
torrentInfo.DownloadUrl.Should().Be("http://titansof.tv/api/torrents/19445/download?apikey=abc");
|
||||
torrentInfo.InfoUrl.Should().Be("http://titansof.tv/series/287053/episode/5453241");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,8 +84,8 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
|
||||
_videoFiles = videoFiles.ToList();
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(c => c.FilterExistingFiles(_videoFiles, It.IsAny<Series>()))
|
||||
.Returns(_videoFiles);
|
||||
.Setup(c => c.FilterExistingFiles(_videoFiles, It.IsAny<Series>()))
|
||||
.Returns(_videoFiles);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -180,7 +180,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_file_quality_if_folder_quality_is_lower_than_file_quality()
|
||||
public void should_use_file_quality_if_file_quality_was_determined_by_name()
|
||||
{
|
||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||
var expectedQuality = QualityParser.ParseQuality(_videoFiles.Single());
|
||||
@@ -191,10 +191,32 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_folder_quality_when_it_is_greater_than_file_quality()
|
||||
public void should_use_folder_quality_when_file_quality_was_determined_by_the_extension()
|
||||
{
|
||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||
var expectedQuality = new QualityModel(Quality.Bluray1080p);
|
||||
GivenVideoFiles(new string[] { @"C:\Test\Unsorted\The.Office.S03E115.mkv".AsOsAgnostic() });
|
||||
|
||||
_localEpisode.Path = _videoFiles.Single();
|
||||
_localEpisode.Quality.QualitySource = QualitySource.Extension;
|
||||
_localEpisode.Quality.Quality = Quality.HDTV720p;
|
||||
|
||||
var expectedQuality = new QualityModel(Quality.SDTV);
|
||||
|
||||
var result = Subject.GetImportDecisions(_videoFiles, _series, new ParsedEpisodeInfo { Quality = expectedQuality }, true);
|
||||
|
||||
result.Single().LocalEpisode.Quality.Should().Be(expectedQuality);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_folder_quality_when_greater_than_file_quality()
|
||||
{
|
||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||
GivenVideoFiles(new string[] { @"C:\Test\Unsorted\The.Office.S03E115.mkv".AsOsAgnostic() });
|
||||
|
||||
_localEpisode.Path = _videoFiles.Single();
|
||||
_localEpisode.Quality.Quality = Quality.HDTV720p;
|
||||
|
||||
var expectedQuality = new QualityModel(Quality.Bluray720p);
|
||||
|
||||
var result = Subject.GetImportDecisions(_videoFiles, _series, new ParsedEpisodeInfo { Quality = expectedQuality }, true);
|
||||
|
||||
|
||||
@@ -44,25 +44,25 @@
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\AutoMoq.1.6.2.0\lib\net35\AutoMoq.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="FluentAssertions, Version=3.4.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.3.4.0\lib\net40\FluentAssertions.dll</HintPath>
|
||||
<Reference Include="FluentAssertions, Version=4.2.1.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.4.2.1\lib\net40\FluentAssertions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FluentAssertions.Core, Version=3.4.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.3.4.0\lib\net40\FluentAssertions.Core.dll</HintPath>
|
||||
<Reference Include="FluentAssertions.Core, Version=4.2.1.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.4.2.1\lib\net40\FluentAssertions.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FluentMigrator, Version=1.3.1.0, Culture=neutral, PublicKeyToken=aacfc7de5acabf05, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\FluentMigrator.1.3.1.0\lib\40\FluentMigrator.dll</HintPath>
|
||||
<Reference Include="FluentMigrator, Version=1.6.1.0, Culture=neutral, PublicKeyToken=aacfc7de5acabf05, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentMigrator.1.6.1\lib\40\FluentMigrator.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FluentMigrator.Runner, Version=1.3.1.0, Culture=neutral, PublicKeyToken=aacfc7de5acabf05, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\FluentMigrator.Runner.1.3.1.0\lib\40\FluentMigrator.Runner.dll</HintPath>
|
||||
<Reference Include="FluentMigrator.Runner, Version=1.6.1.0, Culture=neutral, PublicKeyToken=aacfc7de5acabf05, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentMigrator.Runner.1.6.1\lib\40\FluentMigrator.Runner.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FluentValidation, Version=5.5.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\FluentValidation.5.5.0.0\lib\Net40\FluentValidation.dll</HintPath>
|
||||
<Reference Include="FluentValidation, Version=6.0.2.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentValidation.6.0.2.0\lib\portable-net40+sl50+wp80+win8+wpa81\FluentValidation.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
@@ -161,8 +161,10 @@
|
||||
<Compile Include="Download\DownloadClientTests\DelugeTests\DelugeFixture.cs" />
|
||||
<Compile Include="Download\DownloadClientTests\DownloadClientFixtureBase.cs" />
|
||||
<Compile Include="Download\DownloadClientTests\NzbgetTests\NzbgetFixture.cs" />
|
||||
<Compile Include="Download\DownloadClientTests\NzbVortexTests\NzbVortexFixture.cs" />
|
||||
<Compile Include="Download\DownloadClientTests\PneumaticProviderFixture.cs" />
|
||||
<Compile Include="Download\DownloadClientTests\RTorrentTests\RTorrentFixture.cs" />
|
||||
<Compile Include="Download\DownloadClientTests\QBittorrentTests\QBittorrentFixture.cs" />
|
||||
<Compile Include="Download\DownloadClientTests\SabnzbdTests\SabnzbdFixture.cs" />
|
||||
<Compile Include="Download\DownloadClientTests\TransmissionTests\TransmissionFixture.cs" />
|
||||
<Compile Include="Download\DownloadClientTests\UTorrentTests\UTorrentFixture.cs" />
|
||||
@@ -222,7 +224,6 @@
|
||||
<Compile Include="IndexerTests\IndexerStatusServiceFixture.cs" />
|
||||
<Compile Include="IndexerTests\IntegrationTests\IndexerIntegrationTests.cs" />
|
||||
<Compile Include="IndexerTests\RarbgTests\RarbgFixture.cs" />
|
||||
<Compile Include="IndexerTests\TitansOfTvTests\TitansOfTvFixture.cs" />
|
||||
<Compile Include="IndexerTests\TorrentRssIndexerTests\TorrentRssParserFactoryFixture.cs" />
|
||||
<Compile Include="IndexerTests\TorrentRssIndexerTests\TorrentRssSettingsDetectorFixture.cs" />
|
||||
<Compile Include="IndexerTests\TorznabTests\TorznabFixture.cs" />
|
||||
@@ -508,9 +509,6 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Files\Indexers\TitansOfTv\RecentFeed.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="Files\TestArchive.tar.gz">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
@@ -546,7 +544,6 @@
|
||||
<PostBuildEvent>
|
||||
</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
@@ -554,4 +551,4 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -57,6 +57,11 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
_episodeFile.Quality.Revision.Version = 2;
|
||||
}
|
||||
|
||||
private void GivenReal()
|
||||
{
|
||||
_episodeFile.Quality.Revision.Real = 1;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_replace_Series_space_Title()
|
||||
{
|
||||
@@ -207,6 +212,16 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
.Should().Be("Proper");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_replace_quality_real_with_real()
|
||||
{
|
||||
_namingConfig.StandardEpisodeFormat = "{Quality Real}";
|
||||
GivenReal();
|
||||
|
||||
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
|
||||
.Should().Be("REAL");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_replace_all_contents_in_pattern()
|
||||
{
|
||||
@@ -617,6 +632,16 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
.Should().Be("South Park - S15E06 [HDTV-720p Proper]");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_replace_quality_full_with_quality_title_and_real_when_a_real()
|
||||
{
|
||||
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} [{Quality Full}]";
|
||||
GivenReal();
|
||||
|
||||
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
|
||||
.Should().Be("South Park - S15E06 [HDTV-720p REAL]");
|
||||
}
|
||||
|
||||
[TestCase(' ')]
|
||||
[TestCase('-')]
|
||||
[TestCase('.')]
|
||||
|
||||
@@ -82,6 +82,9 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("[Hatsuyuki] Dragon Ball Kai (2014) - 018 (116) [1280x720][C4A3B16E]", "Dragon Ball Kai (2014)", 18, 0, 0)]
|
||||
[TestCase("Dragon Ball Kai (2014) - 39 (137) [v2][720p.HDTV][Unison Fansub]", "Dragon Ball Kai (2014)", 39, 0, 0)]
|
||||
[TestCase("[HorribleSubs] Eyeshield 21 - 101 [480p].mkv", "Eyeshield 21", 101, 0, 0)]
|
||||
[TestCase("[Cthuyuu].Taimadou.Gakuen.35.Shiken.Shoutai.-.03.[720p.H264.AAC][8AD82C3A]", "Taimadou Gakuen 35 Shiken Shoutai", 3, 0, 0)]
|
||||
//[TestCase("Taimadou.Gakuen.35.Shiken.Shoutai.-.03.(1280x720.HEVC.AAC)", "Taimadou Gakuen 35 Shiken Shoutai", 3, 0, 0)]
|
||||
[TestCase("[Cthuyuu] Taimadou Gakuen 35 Shiken Shoutai - 03 [720p H264 AAC][8AD82C3A]", "Taimadou Gakuen 35 Shiken Shoutai", 3, 0, 0)]
|
||||
//[TestCase("", "", 0, 0, 0)]
|
||||
public void should_parse_absolute_numbers(string postTitle, string title, int absoluteEpisodeNumber, int seasonNumber, int episodeNumber)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using FluentAssertions;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
@@ -14,18 +13,19 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Chuck.S03E17.REAL.PROPER.720p.HDTV.x264-ORENJI-RP", 1)]
|
||||
[TestCase("Covert.Affairs.S05E09.REAL.PROPER.HDTV.x264-KILLERS", 1)]
|
||||
[TestCase("Mythbusters.S14E01.REAL.PROPER.720p.HDTV.x264-KILLERS", 1)]
|
||||
[TestCase("Orange.Is.the.New.Black.s02e06.real.proper.720p.webrip.x264-2hd", 1)]
|
||||
[TestCase("Top.Gear.S21E07.Super.Duper.Real.Proper.HDTV.x264-FTP", 1)]
|
||||
[TestCase("Orange.Is.the.New.Black.s02e06.real.proper.720p.webrip.x264-2hd", 0)]
|
||||
[TestCase("Top.Gear.S21E07.Super.Duper.Real.Proper.HDTV.x264-FTP", 0)]
|
||||
[TestCase("Top.Gear.S21E07.PROPER.HDTV.x264-RiVER-RP", 0)]
|
||||
[TestCase("House.S07E11.PROPER.REAL.RERIP.1080p.BluRay.x264-TENEIGHTY", 1)]
|
||||
[TestCase("[MGS] - Kuragehime - Episode 02v2 - [D8B6C90D]", 0)]
|
||||
[TestCase("[Hatsuyuki] Tokyo Ghoul - 07 [v2][848x480][23D8F455].avi", 0)]
|
||||
[TestCase("[DeadFish] Barakamon - 01v3 [720p][AAC]", 0)]
|
||||
[TestCase("[DeadFish] Momo Kyun Sword - 01v4 [720p][AAC]", 0)]
|
||||
[TestCase("The Real Housewives of Some Place - S01E01 - Why are we doing this?", 0)]
|
||||
public void should_parse_reality_from_title(string title, int reality)
|
||||
{
|
||||
//TODO: re-enable this when we have a reliable way to determine real
|
||||
//QualityParser.ParseQuality(title).Revision.Real.Should().Be(reality);
|
||||
QualityParser.ParseQuality(title).Revision.Real.Should().Be(reality);
|
||||
}
|
||||
|
||||
[TestCase("Chuck.S04E05.HDTV.XviD-LOL", 1)]
|
||||
|
||||
@@ -42,7 +42,21 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
//[TestCase("Adventure Time - 5x01 - x02 - Finn the Human (2) & Jake the Dog (3)", "Adventure Time", 5, new [] { 1, 2 })]
|
||||
[TestCase("The Young And The Restless - S42 Ep10718 - Ep10722", "The Young And The Restless", 42, new[] { 10718, 10719, 10720, 10721, 10722 })]
|
||||
[TestCase("The Young And The Restless - S42 Ep10688 - Ep10692", "The Young And The Restless", 42, new[] { 10688, 10689, 10690, 10691, 10692 })]
|
||||
//[TestCase("", "", ,new [] { })]
|
||||
[TestCase("RWBY.S01E02E03.1080p.BluRay.x264-DeBTViD", "RWBY", 1, new [] { 2, 3 })]
|
||||
[TestCase("grp-zoos01e11e12-1080p", "grp-zoo", 1, new [] { 11, 12 })]
|
||||
[TestCase("grp-zoo-s01e11e12-1080p", "grp-zoo", 1, new [] { 11, 12 })]
|
||||
[TestCase("Series Title.S6.E1.E2.Episode Name.1080p.WEB-DL", "Series Title", 6, new [] { 1, 2 })]
|
||||
[TestCase("Series Title.S6E1-E2.Episode Name.1080p.WEB-DL", "Series Title", 6, new [] { 1, 2 })]
|
||||
[TestCase("Series Title.S6E1-S6E2.Episode Name.1080p.WEB-DL", "Series Title", 6, new [] { 1, 2 })]
|
||||
[TestCase("Series Title.S6E1E2.Episode Name.1080p.WEB-DL", "Series Title", 6, new [] { 1, 2 })]
|
||||
[TestCase("Series Title.S6E1-E2-E3.Episode Name.1080p.WEB-DL", "Series Title", 6, new [] { 1, 2, 3})]
|
||||
[TestCase("Series Title.S6.E1E3.Episode Name.1080p.WEB-DL", "Series Title", 6, new [] { 1, 2, 3 })]
|
||||
[TestCase("Series Title.S6.E1-E2.Episode Name.1080p.WEB-DL", "Series Title", 6, new[] { 1, 2 })]
|
||||
[TestCase("Series Title.S6.E1-S6E2.Episode Name.1080p.WEB-DL", "Series Title", 6, new[] { 1, 2 })]
|
||||
[TestCase("Series Title.S6.E1E2.Episode Name.1080p.WEB-DL", "Series Title", 6, new[] { 1, 2 })]
|
||||
[TestCase("Series Title.S6.E1-E2-E3.Episode Name.1080p.WEB-DL", "Series Title", 6, new[] { 1, 2, 3 })]
|
||||
[TestCase("Series Title.S6.E1E3.Episode Name.1080p.WEB-DL", "Series Title", 6, new[] { 1, 2, 3 })]
|
||||
//[TestCase("", "", , new [] { })]
|
||||
public void should_parse_multiple_episodes(string postTitle, string title, int season, int[] episodes)
|
||||
{
|
||||
var result = Parser.Parser.ParseTitle(postTitle);
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Match of the Day 2", "matchday2")]
|
||||
[TestCase("[ www.Torrenting.com ] - Revenge.S03E14.720p.HDTV.X264-DIMENSION", "Revenge")]
|
||||
[TestCase("Seed S02E09 HDTV x264-2HD [eztv]-[rarbg.com]", "Seed")]
|
||||
[TestCase("Reno.911.S01.DVDRip.DD2.0.x264-DEEP", "Reno 911")]
|
||||
public void should_parse_series_name(string postTitle, string title)
|
||||
{
|
||||
var result = Parser.Parser.ParseSeriesName(postTitle).CleanSeriesTitle();
|
||||
@@ -46,7 +47,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[Test]
|
||||
public void should_remove_accents_from_title()
|
||||
{
|
||||
const string title = "Carnivŕle";
|
||||
const string title = "Carniv\u00E0le";
|
||||
|
||||
title.CleanSeriesTitle().Should().Be("carnivale");
|
||||
}
|
||||
|
||||
@@ -204,6 +204,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
|
||||
[TestCase("Sonny.With.a.Chance.S02E15", false)]
|
||||
[TestCase("Law & Order: Special Victims Unit - 11x11 - Quickie", false)]
|
||||
[TestCase("Series.Title.S01E01.webm", false)]
|
||||
public void quality_parse(string title, bool proper)
|
||||
{
|
||||
ParseAndVerifyQuality(title, Quality.Unknown, proper);
|
||||
@@ -228,6 +229,24 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase("Saturday.Night.Live.Vintage.S10E09.Eddie.Murphy.The.Honeydrippers.1080i.UPSCALE.HDTV.DD5.1.MPEG2-zebra")]
|
||||
[TestCase("Dexter - S01E01 - Title [HDTV-1080p]")]
|
||||
[TestCase("[CR] Sailor Moon - 004 [480p][48CE2D0F]")]
|
||||
[TestCase("White.Van.Man.2011.S02E01.WS.PDTV.x264-REPACK-TLA")]
|
||||
public void should_parse_quality_from_name(string title)
|
||||
{
|
||||
QualityParser.ParseQuality(title).QualitySource.Should().Be(QualitySource.Name);
|
||||
}
|
||||
|
||||
[TestCase("Revolution.S01E02.Chained.Heat.mkv")]
|
||||
[TestCase("Dexter - S01E01 - Title.avi")]
|
||||
[TestCase("the_x-files.9x18.sunshine_days.avi")]
|
||||
[TestCase("[CR] Sailor Moon - 004 [48CE2D0F].avi")]
|
||||
public void should_parse_quality_from_extension(string title)
|
||||
{
|
||||
QualityParser.ParseQuality(title).QualitySource.Should().Be(QualitySource.Extension);
|
||||
}
|
||||
|
||||
private void ParseAndVerifyQuality(string title, Quality quality, bool proper)
|
||||
{
|
||||
var result = QualityParser.ParseQuality(title);
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Series Title S01E01 Episode Title", null)]
|
||||
[TestCase("The Colbert Report - 2014-06-02 - Thomas Piketty.mkv", null)]
|
||||
[TestCase("Real Time with Bill Maher S12E17 May 23, 2014.mp4", null)]
|
||||
[TestCase("Reizen Waes - S01E08 - Transistrië, Zuid-Ossetië en Abchazië SDTV.avi", null)]
|
||||
[TestCase("Reizen Waes - S01E08 - Transistri\u00EB, Zuid-Osseti\u00EB en Abchazi\u00EB SDTV.avi", null)]
|
||||
[TestCase("Simpsons 10x11 - Wild Barts Cant Be Broken [rl].avi", null)]
|
||||
[TestCase("[ www.Torrenting.com ] - Revenge.S03E14.720p.HDTV.X264-DIMENSION", "DIMENSION")]
|
||||
[TestCase("Seed S02E09 HDTV x264-2HD [eztv]-[rarbg.com]", "2HD")]
|
||||
|
||||
@@ -111,6 +111,13 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Series Title Season 01 Episode 05 720p", "Series Title", 1, 5)]
|
||||
//[TestCase("Off the Air - 101 - Animals (460p.x264.vorbis-2.0) [449].mkv", "Off the Air", 1, 1)]
|
||||
[TestCase("The Young And the Restless - S42 E10713 - 2015-07-20.mp4", "The Young And the Restless", 42, 10713)]
|
||||
[TestCase("quantico.103.hdtv-lol[ettv].mp4", "quantico", 1, 3)]
|
||||
[TestCase("Fargo - 01x02 - The Rooster Prince - [itz_theo]", "Fargo", 1, 2)]
|
||||
[TestCase("Castle (2009) - [06x16] - Room 147.mp4", "Castle (2009)", 6, 16)]
|
||||
[TestCase("grp-zoos01e11-1080p", "grp-zoo", 1, 11)]
|
||||
[TestCase("grp-zoo-s01e11-1080p", "grp-zoo", 1, 11)]
|
||||
[TestCase("Jeopardy!.S2016E14.2016-01-20.avi", "Jeopardy!", 2016, 14)]
|
||||
[TestCase("Ken.Burns.The.Civil.War.5of9.The.Universe.Of.Battle.1990.DVDRip.x264-HANDJOB", "Ken Burns The Civil War", 1, 5)]
|
||||
//[TestCase("", "", 0, 0)]
|
||||
public void should_parse_single_episode(string postTitle, string title, int seasonNumber, int episodeNumber)
|
||||
{
|
||||
|
||||
@@ -18,7 +18,6 @@ namespace NzbDrone.Core.Test.TvTests
|
||||
{
|
||||
_series = Builder<Series>.CreateNew()
|
||||
.With(v => v.Status == SeriesStatusType.Continuing)
|
||||
.With(v => v.LastInfoSync == DateTime.UtcNow.AddHours(-12))
|
||||
.Build();
|
||||
|
||||
Mocker.GetMock<IEpisodeService>()
|
||||
@@ -45,6 +44,11 @@ namespace NzbDrone.Core.Test.TvTests
|
||||
_series.LastInfoSync = DateTime.UtcNow.AddDays(-1);
|
||||
}
|
||||
|
||||
private void GivenSeriesLastRefreshedHalfADayAgo()
|
||||
{
|
||||
_series.LastInfoSync = DateTime.UtcNow.AddHours(-12);
|
||||
}
|
||||
|
||||
private void GivenSeriesLastRefreshedRecently()
|
||||
{
|
||||
_series.LastInfoSync = DateTime.UtcNow.AddHours(-1);
|
||||
@@ -66,6 +70,8 @@ namespace NzbDrone.Core.Test.TvTests
|
||||
[Test]
|
||||
public void should_return_true_if_running_series_last_refreshed_more_than_6_hours_ago()
|
||||
{
|
||||
GivenSeriesLastRefreshedHalfADayAgo();
|
||||
|
||||
Subject.ShouldRefresh(_series).Should().BeTrue();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<packages>
|
||||
<package id="AutoMoq" version="1.6.2.0" targetFramework="net40" />
|
||||
<package id="CommonServiceLocator" version="1.0" targetFramework="net40" />
|
||||
<package id="FluentAssertions" version="3.4.0" targetFramework="net40" />
|
||||
<package id="FluentMigrator" version="1.3.1.0" targetFramework="net40" />
|
||||
<package id="FluentMigrator.Runner" version="1.3.1.0" targetFramework="net40" />
|
||||
<package id="FluentValidation" version="5.5.0.0" targetFramework="net40" />
|
||||
<package id="FluentAssertions" version="4.2.1" targetFramework="net40" />
|
||||
<package id="FluentMigrator" version="1.6.1" targetFramework="net40" />
|
||||
<package id="FluentMigrator.Runner" version="1.6.1" targetFramework="net40" />
|
||||
<package id="FluentValidation" version="6.0.2.0" targetFramework="net40" />
|
||||
<package id="Moq" version="4.0.10827" />
|
||||
<package id="NBuilder" version="3.0.1.1" />
|
||||
<package id="NCrunch.Framework" version="1.46.0.9" targetFramework="net40" />
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace NzbDrone.Core.Annotations
|
||||
Checkbox,
|
||||
Select,
|
||||
Path,
|
||||
FilePath,
|
||||
Hidden,
|
||||
Tag,
|
||||
Action,
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Configuration
|
||||
public interface IConfigRepository : IBasicRepository<Config>
|
||||
{
|
||||
Config Get(string key);
|
||||
|
||||
Config Upsert(string key, string value);
|
||||
}
|
||||
|
||||
public class ConfigRepository : BasicRepository<Config>, IConfigRepository
|
||||
@@ -23,5 +23,19 @@ namespace NzbDrone.Core.Configuration
|
||||
{
|
||||
return Query.Where(c => c.Key == key).SingleOrDefault();
|
||||
}
|
||||
|
||||
public Config Upsert(string key, string value)
|
||||
{
|
||||
var dbValue = Get(key);
|
||||
|
||||
if (dbValue == null)
|
||||
{
|
||||
return Insert(new Config {Key = key, Value = value});
|
||||
}
|
||||
|
||||
dbValue.Value = value;
|
||||
|
||||
return Update(dbValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,12 +30,7 @@ namespace NzbDrone.Core.Configuration
|
||||
_cache = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public IEnumerable<Config> All()
|
||||
{
|
||||
return _repository.All();
|
||||
}
|
||||
|
||||
public Dictionary<string, object> AllWithDefaults()
|
||||
private Dictionary<string, object> AllWithDefaults()
|
||||
{
|
||||
var dict = new Dictionary<string, object>(StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
@@ -45,7 +40,6 @@ namespace NzbDrone.Core.Configuration
|
||||
foreach (var propertyInfo in properties)
|
||||
{
|
||||
var value = propertyInfo.GetValue(this, null);
|
||||
|
||||
dict.Add(propertyInfo.Name, value);
|
||||
}
|
||||
|
||||
@@ -65,7 +59,9 @@ namespace NzbDrone.Core.Configuration
|
||||
var equal = configValue.Value.ToString().Equals(currentValue.ToString());
|
||||
|
||||
if (!equal)
|
||||
{
|
||||
SetValue(configValue.Key, configValue.Value.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new ConfigSavedEvent());
|
||||
@@ -331,7 +327,7 @@ namespace NzbDrone.Core.Configuration
|
||||
return Convert.ToInt32(GetValue(key, defaultValue));
|
||||
}
|
||||
|
||||
public T GetValueEnum<T>(string key, T defaultValue)
|
||||
private T GetValueEnum<T>(string key, T defaultValue)
|
||||
{
|
||||
return (T)Enum.Parse(typeof(T), GetValue(key, defaultValue), true);
|
||||
}
|
||||
@@ -346,7 +342,9 @@ namespace NzbDrone.Core.Configuration
|
||||
string dbValue;
|
||||
|
||||
if (_cache.TryGetValue(key, out dbValue) && dbValue != null && !string.IsNullOrEmpty(dbValue))
|
||||
{
|
||||
return dbValue;
|
||||
}
|
||||
|
||||
_logger.Trace("Using default config value for '{0}' defaultValue:'{1}'", key, defaultValue);
|
||||
|
||||
@@ -354,6 +352,7 @@ namespace NzbDrone.Core.Configuration
|
||||
{
|
||||
SetValue(key, defaultValue.ToString());
|
||||
}
|
||||
|
||||
return defaultValue.ToString();
|
||||
}
|
||||
|
||||
@@ -367,44 +366,34 @@ namespace NzbDrone.Core.Configuration
|
||||
SetValue(key, value.ToString());
|
||||
}
|
||||
|
||||
public void SetValue(string key, string value)
|
||||
private void SetValue(string key, Enum value)
|
||||
{
|
||||
SetValue(key, value.ToString().ToLower());
|
||||
}
|
||||
|
||||
private void SetValue(string key, string value)
|
||||
{
|
||||
key = key.ToLowerInvariant();
|
||||
|
||||
_logger.Trace("Writing Setting to database. Key:'{0}' Value:'{1}'", key, value);
|
||||
|
||||
var dbValue = _repository.Get(key);
|
||||
|
||||
if (dbValue == null)
|
||||
{
|
||||
_repository.Insert(new Config { Key = key, Value = value });
|
||||
}
|
||||
else
|
||||
{
|
||||
dbValue.Value = value;
|
||||
_repository.Update(dbValue);
|
||||
}
|
||||
_repository.Upsert(key, value);
|
||||
|
||||
ClearCache();
|
||||
}
|
||||
|
||||
public void SetValue(string key, Enum value)
|
||||
{
|
||||
SetValue(key, value.ToString().ToLower());
|
||||
}
|
||||
|
||||
private void EnsureCache()
|
||||
{
|
||||
lock (_cache)
|
||||
{
|
||||
if (!_cache.Any())
|
||||
{
|
||||
_cache = All().ToDictionary(c => c.Key.ToLower(), c => c.Value);
|
||||
var all = _repository.All();
|
||||
_cache = all.ToDictionary(c => c.Key.ToLower(), c => c.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ClearCache()
|
||||
private static void ClearCache()
|
||||
{
|
||||
lock (_cache)
|
||||
{
|
||||
|
||||
@@ -6,8 +6,6 @@ namespace NzbDrone.Core.Configuration
|
||||
{
|
||||
public interface IConfigService
|
||||
{
|
||||
IEnumerable<Config> All();
|
||||
Dictionary<string, object> AllWithDefaults();
|
||||
void SaveConfigDictionary(Dictionary<string, object> configValues);
|
||||
|
||||
bool IsDefined(string key);
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(98)]
|
||||
public class remove_titans_of_tv : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Delete.FromTable("Indexers").Row(new { Implementation = "TitansOfTv" });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ using FluentMigrator.Runner.Processors.SQLite;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
{
|
||||
public class NzbDroneSqliteProcessor : SqliteProcessor
|
||||
public class NzbDroneSqliteProcessor : SQLiteProcessor
|
||||
{
|
||||
public NzbDroneSqliteProcessor(IDbConnection connection, IMigrationGenerator generator, IAnnouncer announcer, IMigrationProcessorOptions options, FluentMigrator.Runner.Processors.IDbFactory factory)
|
||||
: base(connection, generator, announcer, options, factory)
|
||||
@@ -70,7 +70,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
|
||||
protected virtual TableDefinition GetTableSchema(string tableName)
|
||||
{
|
||||
var schemaDumper = new SqliteSchemaDumper(this, Announcer);
|
||||
var schemaDumper = new SqliteSchemaDumper(this, Announcer);
|
||||
var schema = schemaDumper.ReadDbSchema();
|
||||
|
||||
return schema.Single(v => v.Name == tableName);
|
||||
@@ -88,7 +88,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
}
|
||||
|
||||
// What is the cleanest way to do this? Add function to Generator?
|
||||
var quoter = new SqliteQuoter();
|
||||
var quoter = new SQLiteQuoter();
|
||||
var columnsToTransfer = string.Join(", ", tableDefinition.Columns.Select(c => quoter.QuoteColumnName(c.Name)));
|
||||
|
||||
Process(new CreateTableExpression() { TableName = tempTableName, Columns = tableDefinition.Columns.ToList() });
|
||||
|
||||
@@ -6,13 +6,13 @@ using FluentMigrator.Runner.Processors.SQLite;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
{
|
||||
public class NzbDroneSqliteProcessorFactory : SqliteProcessorFactory
|
||||
public class NzbDroneSqliteProcessorFactory : SQLiteProcessorFactory
|
||||
{
|
||||
public override IMigrationProcessor Create(string connectionString, IAnnouncer announcer, IMigrationProcessorOptions options)
|
||||
{
|
||||
var factory = new MigrationDbFactory();
|
||||
var connection = factory.CreateConnection(connectionString);
|
||||
var generator = new SqliteGenerator() { compatabilityMode = CompatabilityMode.STRICT };
|
||||
var generator = new SQLiteGenerator { compatabilityMode = CompatabilityMode.STRICT };
|
||||
return new NzbDroneSqliteProcessor(connection, generator, announcer, options, factory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,14 +11,14 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
// The original implementation had bad support for escaped identifiers, amongst other things.
|
||||
public class SqliteSchemaDumper
|
||||
{
|
||||
public SqliteSchemaDumper(SqliteProcessor processor, IAnnouncer announcer)
|
||||
public SqliteSchemaDumper(SQLiteProcessor processor, IAnnouncer announcer)
|
||||
{
|
||||
Announcer = announcer;
|
||||
Processor = processor;
|
||||
}
|
||||
|
||||
public virtual IAnnouncer Announcer { get; set; }
|
||||
public SqliteProcessor Processor { get; set; }
|
||||
public SQLiteProcessor Processor { get; set; }
|
||||
|
||||
protected internal virtual TableDefinition ReadTableSchema(string sqlSchema)
|
||||
{
|
||||
|
||||
@@ -78,14 +78,18 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, report.TvdbId, report.TvRageId, searchCriteria);
|
||||
remoteEpisode.Release = report;
|
||||
|
||||
if (remoteEpisode.Series != null)
|
||||
if (remoteEpisode.Series == null)
|
||||
{
|
||||
remoteEpisode.DownloadAllowed = remoteEpisode.Episodes.Any();
|
||||
decision = GetDecisionForReport(remoteEpisode, searchCriteria);
|
||||
decision = new DownloadDecision(remoteEpisode, new Rejection("Unknown Series"));
|
||||
}
|
||||
else if (remoteEpisode.Episodes.Empty())
|
||||
{
|
||||
decision = new DownloadDecision(remoteEpisode, new Rejection("Unable to parse episodes from release name"));
|
||||
}
|
||||
else
|
||||
{
|
||||
decision = new DownloadDecision(remoteEpisode, new Rejection("Unknown Series"));
|
||||
remoteEpisode.DownloadAllowed = remoteEpisode.Episodes.Any();
|
||||
decision = GetDecisionForReport(remoteEpisode, searchCriteria);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
return Decision.Reject("{0} is smaller than minimum allowed: {1}", subject.Release.Size.SizeSuffix(), minSize.SizeSuffix());
|
||||
}
|
||||
}
|
||||
if (!qualityDefinition.MaxSize.HasValue)
|
||||
if (!qualityDefinition.MaxSize.HasValue || qualityDefinition.MaxSize.Value == 0)
|
||||
{
|
||||
_logger.Debug("Max size is unlimited - skipping check.");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.History;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -11,14 +12,17 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
private readonly IHistoryService _historyService;
|
||||
private readonly QualityUpgradableSpecification _qualityUpgradableSpecification;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public HistorySpecification(IHistoryService historyService,
|
||||
QualityUpgradableSpecification qualityUpgradableSpecification,
|
||||
IConfigService configService,
|
||||
Logger logger)
|
||||
{
|
||||
_historyService = historyService;
|
||||
_qualityUpgradableSpecification = qualityUpgradableSpecification;
|
||||
_configService = configService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -32,22 +36,43 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
var cdhEnabled = _configService.EnableCompletedDownloadHandling;
|
||||
|
||||
_logger.Debug("Performing history status check on report");
|
||||
foreach (var episode in subject.Episodes)
|
||||
{
|
||||
_logger.Debug("Checking current status of episode [{0}] in history", episode.Id);
|
||||
var mostRecent = _historyService.MostRecentForEpisode(episode.Id);
|
||||
|
||||
if (mostRecent != null && mostRecent.EventType == HistoryEventType.Grabbed && mostRecent.DownloadId.IsNullOrWhiteSpace() && mostRecent.Date.After(DateTime.UtcNow.AddHours(-12)))
|
||||
if (mostRecent != null && mostRecent.EventType == HistoryEventType.Grabbed)
|
||||
{
|
||||
if (!_qualityUpgradableSpecification.CutoffNotMet(subject.Series.Profile, mostRecent.Quality, subject.ParsedEpisodeInfo.Quality))
|
||||
var recent = mostRecent.Date.After(DateTime.UtcNow.AddHours(-12));
|
||||
var cutoffUnmet = _qualityUpgradableSpecification.CutoffNotMet(subject.Series.Profile, mostRecent.Quality, subject.ParsedEpisodeInfo.Quality);
|
||||
var upgradeable = _qualityUpgradableSpecification.IsUpgradable(subject.Series.Profile, mostRecent.Quality, subject.ParsedEpisodeInfo.Quality);
|
||||
|
||||
if (!recent && cdhEnabled)
|
||||
{
|
||||
return Decision.Reject("Recent grab event in history already meets cutoff: {0}", mostRecent.Quality);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.Profile, mostRecent.Quality, subject.ParsedEpisodeInfo.Quality))
|
||||
if (!cutoffUnmet)
|
||||
{
|
||||
return Decision.Reject("Recent grab event in history is of equal or higher quality: {0}", mostRecent.Quality);
|
||||
if (recent)
|
||||
{
|
||||
return Decision.Reject("Recent grab event in history already meets cutoff: {0}", mostRecent.Quality);
|
||||
}
|
||||
|
||||
return Decision.Reject("CDH is disabled and grab event in history already meets cutoff: {0}", mostRecent.Quality);
|
||||
}
|
||||
|
||||
if (!upgradeable)
|
||||
{
|
||||
if (recent)
|
||||
{
|
||||
return Decision.Reject("Recent grab event in history is of equal or higher quality: {0}", mostRecent.Quality);
|
||||
}
|
||||
|
||||
return Decision.Reject("CDH is disabled and grab event in history is of equal or higher quality: {0}", mostRecent.Quality);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
}
|
||||
|
||||
var restClient = RestClientFactory.BuildClient(url);
|
||||
restClient.Timeout = 4000;
|
||||
restClient.Timeout = 15000;
|
||||
|
||||
if (_authPassword != settings.Password || _authCookieContainer == null)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.NzbVortex.JsonConverters
|
||||
{
|
||||
public class NzbVortexLoginResultTypeConverter : JsonConverter
|
||||
{
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var priorityType = (NzbVortexLoginResultType)value;
|
||||
writer.WriteValue(priorityType.ToString().ToLower());
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var result = reader.Value.ToString().Replace("_", string.Empty);
|
||||
|
||||
NzbVortexLoginResultType output;
|
||||
Enum.TryParse(result, true, out output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(NzbVortexLoginResultType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.NzbVortex.JsonConverters
|
||||
{
|
||||
public class NzbVortexResultTypeConverter : JsonConverter
|
||||
{
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var priorityType = (NzbVortexResultType)value;
|
||||
writer.WriteValue(priorityType.ToString().ToLower());
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var result = reader.Value.ToString().Replace("_", string.Empty);
|
||||
|
||||
NzbVortexResultType output;
|
||||
Enum.TryParse(result, true, out output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(NzbVortexResultType);
|
||||
}
|
||||
}
|
||||
}
|
||||
265
src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs
Normal file
265
src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs
Normal file
@@ -0,0 +1,265 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
using NzbDrone.Core.RemotePathMappings;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
{
|
||||
public class NzbVortex : UsenetClientBase<NzbVortexSettings>
|
||||
{
|
||||
private readonly INzbVortexProxy _proxy;
|
||||
|
||||
public NzbVortex(INzbVortexProxy proxy,
|
||||
IHttpClient httpClient,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
IRemotePathMappingService remotePathMappingService,
|
||||
Logger logger)
|
||||
: base(httpClient, configService, diskProvider, remotePathMappingService, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
||||
protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContent)
|
||||
{
|
||||
var priority = remoteEpisode.IsRecentEpisode() ? Settings.RecentTvPriority : Settings.OlderTvPriority;
|
||||
|
||||
var response = _proxy.DownloadNzb(fileContent, filename, priority, Settings);
|
||||
|
||||
if (response == null)
|
||||
{
|
||||
throw new DownloadClientException("Failed to add nzb {0}", filename);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return "NZBVortex";
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<DownloadClientItem> GetItems()
|
||||
{
|
||||
NzbVortexQueue vortexQueue;
|
||||
|
||||
try
|
||||
{
|
||||
vortexQueue = _proxy.GetQueue(30, Settings);
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
_logger.Warn("Couldn't get download queue. {0}", ex.Message);
|
||||
return Enumerable.Empty<DownloadClientItem>();
|
||||
}
|
||||
|
||||
var queueItems = new List<DownloadClientItem>();
|
||||
|
||||
foreach (var vortexQueueItem in vortexQueue.Items)
|
||||
{
|
||||
var queueItem = new DownloadClientItem();
|
||||
|
||||
queueItem.DownloadClient = Definition.Name;
|
||||
queueItem.DownloadId = vortexQueueItem.AddUUID ?? vortexQueueItem.Id.ToString();
|
||||
queueItem.Category = vortexQueueItem.GroupName;
|
||||
queueItem.Title = vortexQueueItem.UiTitle;
|
||||
queueItem.TotalSize = vortexQueueItem.TotalDownloadSize;
|
||||
queueItem.RemainingSize = vortexQueueItem.TotalDownloadSize - vortexQueueItem.DownloadedSize;
|
||||
queueItem.RemainingTime = null;
|
||||
|
||||
if (vortexQueueItem.IsPaused)
|
||||
{
|
||||
queueItem.Status = DownloadItemStatus.Paused;
|
||||
}
|
||||
else switch (vortexQueueItem.State)
|
||||
{
|
||||
case NzbVortexStateType.Waiting:
|
||||
queueItem.Status = DownloadItemStatus.Queued;
|
||||
break;
|
||||
case NzbVortexStateType.Done:
|
||||
queueItem.Status = DownloadItemStatus.Completed;
|
||||
break;
|
||||
case NzbVortexStateType.UncompressFailed:
|
||||
case NzbVortexStateType.CheckFailedDataCorrupt:
|
||||
case NzbVortexStateType.BadlyEncoded:
|
||||
queueItem.Status = DownloadItemStatus.Failed;
|
||||
break;
|
||||
default:
|
||||
queueItem.Status = DownloadItemStatus.Downloading;
|
||||
break;
|
||||
}
|
||||
|
||||
queueItem.OutputPath = GetOutputPath(vortexQueueItem, queueItem);
|
||||
|
||||
if (vortexQueueItem.State == NzbVortexStateType.PasswordRequest)
|
||||
{
|
||||
queueItem.IsEncrypted = true;
|
||||
}
|
||||
|
||||
if (queueItem.Status == DownloadItemStatus.Completed)
|
||||
{
|
||||
queueItem.RemainingTime = TimeSpan.Zero;
|
||||
}
|
||||
|
||||
queueItems.Add(queueItem);
|
||||
}
|
||||
|
||||
return queueItems;
|
||||
}
|
||||
|
||||
public override void RemoveItem(string downloadId, bool deleteData)
|
||||
{
|
||||
// Try to find the download by numerical ID, otherwise try by AddUUID
|
||||
int id;
|
||||
|
||||
if (int.TryParse(downloadId, out id))
|
||||
{
|
||||
_proxy.Remove(id, deleteData, Settings);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
var queue = _proxy.GetQueue(30, Settings);
|
||||
var queueItem = queue.Items.FirstOrDefault(c => c.AddUUID == downloadId);
|
||||
|
||||
if (queueItem != null)
|
||||
{
|
||||
_proxy.Remove(queueItem.Id, deleteData, Settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected List<NzbVortexGroup> GetGroups()
|
||||
{
|
||||
return _proxy.GetGroups(Settings);
|
||||
}
|
||||
|
||||
public override DownloadClientStatus GetStatus()
|
||||
{
|
||||
var status = new DownloadClientStatus
|
||||
{
|
||||
IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost"
|
||||
};
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
protected override void Test(List<ValidationFailure> failures)
|
||||
{
|
||||
failures.AddIfNotNull(TestConnection());
|
||||
failures.AddIfNotNull(TestApiVersion());
|
||||
failures.AddIfNotNull(TestAuthentication());
|
||||
failures.AddIfNotNull(TestCategory());
|
||||
}
|
||||
|
||||
private ValidationFailure TestConnection()
|
||||
{
|
||||
try
|
||||
{
|
||||
_proxy.GetVersion(Settings);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException(ex.Message, ex);
|
||||
return new ValidationFailure("Host", "Unable to connect to NZBVortex");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private ValidationFailure TestApiVersion()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = _proxy.GetApiVersion(Settings);
|
||||
var version = new Version(response.ApiLevel);
|
||||
|
||||
if (version.Major < 2 || (version.Major == 2 && version.Minor < 3))
|
||||
{
|
||||
return new ValidationFailure("Host", "NZBVortex needs to be updated");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException(ex.Message, ex);
|
||||
return new ValidationFailure("Host", "Unable to connect to NZBVortex");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private ValidationFailure TestAuthentication()
|
||||
{
|
||||
try
|
||||
{
|
||||
_proxy.GetQueue(1, Settings);
|
||||
}
|
||||
catch (NzbVortexAuthenticationException ex)
|
||||
{
|
||||
return new ValidationFailure("ApiKey", "API Key Incorrect");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private ValidationFailure TestCategory()
|
||||
{
|
||||
var group = GetGroups().FirstOrDefault(c => c.GroupName == Settings.TvCategory);
|
||||
|
||||
if (group == null)
|
||||
{
|
||||
if (Settings.TvCategory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
return new NzbDroneValidationFailure("TvCategory", "Group does not exist")
|
||||
{
|
||||
DetailedDescription = "The Group you entered doesn't exist in NzbVortex. Go to NzbVortex to create it."
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private OsPath GetOutputPath(NzbVortexQueueItem vortexQueueItem, DownloadClientItem queueItem)
|
||||
{
|
||||
var outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(vortexQueueItem.DestinationPath));
|
||||
|
||||
if (outputPath.FileName == vortexQueueItem.UiTitle)
|
||||
{
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
// If the release isn't done yet, skip the files check and return null
|
||||
if (vortexQueueItem.State != NzbVortexStateType.Done)
|
||||
{
|
||||
return new OsPath(null);
|
||||
}
|
||||
|
||||
var filesResponse = _proxy.GetFiles(vortexQueueItem.Id, Settings);
|
||||
|
||||
if (filesResponse.Files.Count > 1)
|
||||
{
|
||||
var message = string.Format("Download contains multiple files and is not in a job folder: {0}", outputPath);
|
||||
|
||||
queueItem.Status = DownloadItemStatus.Warning;
|
||||
queueItem.Message = message;
|
||||
|
||||
_logger.Debug(message);
|
||||
}
|
||||
|
||||
return new OsPath(Path.Combine(outputPath.FullPath, filesResponse.Files.First().FileName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
{
|
||||
class NzbVortexAuthenticationException : DownloadClientException
|
||||
{
|
||||
public NzbVortexAuthenticationException(string message, params object[] args) : base(message, args)
|
||||
{
|
||||
}
|
||||
|
||||
public NzbVortexAuthenticationException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public NzbVortexAuthenticationException(string message, Exception innerException, params object[] args) : base(message, innerException, args)
|
||||
{
|
||||
}
|
||||
|
||||
public NzbVortexAuthenticationException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
{
|
||||
public class NzbVortexFile
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string FileName { get; set; }
|
||||
public NzbVortexStateType State { get; set; }
|
||||
public long DileSize { get; set; }
|
||||
public long DownloadedSize { get; set; }
|
||||
public long TotalDownloadedSize { get; set; }
|
||||
public bool ExtractPasswordRequired { get; set; }
|
||||
public string ExtractPassword { get; set; }
|
||||
public long PostDate { get; set; }
|
||||
public bool Crc32CheckFailed { get; set; }
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user