1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-18 21:35:51 -04:00

Compare commits

...

89 Commits

Author SHA1 Message Date
Donald Webster
8ca66fb61a Change smtp.google.com to smtp.gmail.com (#1410) 2017-04-18 19:28:15 +02:00
Leonardo Galli
064844ac0c Fix PTP_Approved turning into HDBits Internal. 2017-04-18 16:41:40 +02:00
Leonardo Galli
5540594ecf Fix ptp tests. 2017-04-17 22:30:51 +02:00
Leonardo Galli
433ae019de AHD, PTP and HDB support the new indexer flags too now! Indexer flags can be preferred over other releases. 2017-04-17 17:12:09 +02:00
Leonardo Galli
10091b9454 Movies with Umlauts are now correctly matched and have correct CleanTitles.
An update library is recommended. Fixes #1338
2017-04-17 13:08:47 +02:00
Leonardo Galli
475851775f Minor Text fixes. 2017-04-17 12:11:35 +02:00
Leonardo Galli
0fff862fd2 Updated debug movie title to include Year. 2017-04-17 12:09:24 +02:00
Leonardo Galli
23754c49dc Fix error when MinimumAvailability was Announced and Delay was negative. 2017-04-17 12:08:03 +02:00
Leonardo Galli
fbf790e9fd Disable PreDB sync for now. 2017-04-17 12:04:54 +02:00
Leonardo Galli
6d00bd0f7a Stats are now sent to our server instead of Sonarr's :) 2017-04-17 12:01:56 +02:00
hotio
5bf95e0d9e Update Series reference to Movies, should fix #1399 (#1402) 2017-04-16 23:20:42 +02:00
Leonardo Galli
8bb4b02be7 Fix for sql error. Did not think everything through exactly.
Fixes #1390.
2017-04-16 00:48:39 +02:00
Leonardo Galli
b26a036eed Fix when MovieTitle is the empty string (should not occur, but what evs)
Fixes #1389
2017-04-16 00:26:16 +02:00
Leonardo Galli
ef57882291 Fixes Movie Size not showing correctly.
Fixes #1379
2017-04-15 15:47:40 +02:00
Leonardo Galli
32a2407ad1 Fixed an issue where movies which were labelled with an alternative title could not be found.
Fixes #557 Fixes #1387 Probably fixes #1372, probably fixes #555
2017-04-15 14:50:34 +02:00
Leonardo Galli
33b48eec95 Indexer flags implementation. (#1377) Will be further finalized over the next few weeks with Freelech, preferring of certain flags, etc 2017-04-14 22:27:48 +02:00
Leonardo Galli
3790dc9109 Added test for fix in last commit. 2017-04-13 14:18:48 +02:00
Leonardo Galli
343d849536 Add default runtime when runtime info of tmdb says 0.
Fixes #1371
2017-04-13 14:04:38 +02:00
Leonardo Galli
c36b259fa9 Fixes an issue where the semicolon and space afterwards was replaced.
This caused issues with cleaning the title afterwards. Fixes #1185
2017-04-13 10:27:00 +02:00
Leonardo Galli
6463913f22 Final tweak for package.sh 2017-04-12 22:31:49 +02:00
Leonardo Galli
e39deb4bdc Update branch. 2017-04-12 21:57:59 +02:00
Leonardo Galli
9ca2c21547 This should finally fix all packaging stuff. 2017-04-12 21:46:33 +02:00
Leonardo Galli
f376360611 Update packages.sh some more. 2017-04-12 21:23:02 +02:00
Leonardo Galli
3e966d4d58 Update package.sh script 2017-04-12 21:20:33 +02:00
Leonardo Galli
e036267c33 Test fixes. 2017-04-12 18:23:04 +02:00
Leonardo Galli
7d4378ca7a More test debugging. 2017-04-12 18:12:47 +02:00
Leonardo Galli
ee1ebfd893 Remote Test debugging yey! 2017-04-12 17:58:52 +02:00
Leonardo Galli
403fd0f0c0 Remove unecessary test. 2017-04-12 17:17:22 +02:00
Leonardo Galli
fc5ac8219f Using NUnit.Runners so that teamcity build works. 2017-04-12 17:03:53 +02:00
Leonardo Galli
600a433faa Merge remote-tracking branch 'origin/develop' into develop 2017-04-12 16:45:23 +02:00
Leonardo Galli
5609facd9d Fixed package script for Teamcity. 2017-04-12 16:45:13 +02:00
Leonardo Galli
36ea6c6b99 Turn installer back on. 2017-04-12 12:23:07 +02:00
Leonardo Galli
56ac87c760 Disabled installer being picked up, causes error with update api. 2017-04-11 21:19:31 +02:00
Leonardo Galli
47753c47a5 Now artifacts get pushed even if tests fail 2017-04-11 19:40:13 +02:00
Leonardo Galli
712c0eb84a Log if ParsedMovieInfo is NULL. 2017-04-11 19:31:33 +02:00
Leonardo Galli
8765155223 Merge remote-tracking branch 'origin/develop' into develop 2017-04-11 12:16:14 +02:00
Leonardo Galli
63d7596e98 Catching predb.me errors hopefully. 2017-04-11 12:15:52 +02:00
Leonardo Galli
640edf0cce Update nzbdrone.iss 2017-04-10 18:34:03 +02:00
Leonardo Galli
280445e756 Update appveyor.yml 2017-04-10 18:00:32 +02:00
Leonardo Galli
bae8d5e9a4 Update build-appveyor.cake 2017-04-10 17:21:52 +02:00
Leonardo Galli
07254adf91 Update appveyor.yml 2017-04-10 16:12:52 +02:00
Leonardo Galli
d33ec334f3 Merge remote-tracking branch 'origin/develop' into develop 2017-04-10 15:54:45 +02:00
Leonardo Galli
a80e9f11f2 Text fixes and got pending releases finally fully working.
Fixes #1318 and fixes #1023
2017-04-10 15:54:05 +02:00
Leonardo Galli
4a0ef984fb Update appveyor.yml 2017-04-10 15:51:36 +02:00
Leonardo Galli
685012280b Update nzbdrone.iss 2017-04-10 15:51:11 +02:00
Leonardo Galli
6963078669 Update build-appveyor.cake 2017-04-10 15:18:11 +02:00
Leonardo Galli
7182081fca Update build-appveyor.cake 2017-04-10 14:52:13 +02:00
Leonardo Galli
87ee360818 Update appveyor.yml 2017-04-10 14:47:07 +02:00
Leonardo Galli
ad222570be Update build-appveyor.cake 2017-04-10 14:45:59 +02:00
Leonardo Galli
680681c8bd Just getting Appveyor to build 2017-04-10 14:32:49 +02:00
Leonardo Galli
e2ae7536ad Update appveyor.yml 2017-04-10 14:32:03 +02:00
Leonardo Galli
98c117a460 Installer should be built too now. 2017-04-10 14:30:55 +02:00
Leonardo Galli
c54f8806b3 Update nzbdrone.iss 2017-04-10 14:29:08 +02:00
Leonardo Galli
454d5c37f9 Update nzbdrone.iss 2017-04-10 14:24:06 +02:00
Leonardo Galli
e9f084fd81 Update README.md 2017-04-10 13:15:06 +02:00
Leonardo Galli
d1bbcdc039 Fixed searching for movie after it is added from a list. 2017-04-10 11:55:50 +02:00
Leonardo Galli
c17deb7d92 Specific Subtitle tags (such as nlsub) can now be whitelisted and will be downloaded.
Fixes #540 and fixes a lot of other requests.
2017-04-10 11:41:08 +02:00
Leonardo Galli
7066b078ab Allow Hardcoded subs to be downloaded still. 2017-04-10 11:17:31 +02:00
Leonardo Galli
b4bb8875d3 Catching HTTP Errors when adding movies from a list. 2017-04-08 13:50:18 +02:00
Mitchell Cash
cb596488f2 SABnzbd 2.0 API compatibility (#1339)
* Fixed: Sabnzbd 2.0 api compatibility.

closes #1775

* fixed sab tests.

* Fixed: Sabnzbd error when tv sorting enabled for all categories.
2017-04-08 13:36:16 +02:00
Mitchell Cash
3403ddf993 Fixed: Zero length file causes MediaInfo hanging in 100% cpu load. (#1340) 2017-04-08 13:34:17 +02:00
Mitchell Cash
17118cf24d Fixed: Newznab default capabilities erroneously cached if indexer is unavailable. (#1341) 2017-04-08 13:34:07 +02:00
Rusk85
27ab70333c Cleanup on mapping logic. Movies with up to 4500 parts are now supported! 2017-04-05 20:44:05 +02:00
rmangahas-coupa
f4031f1e5f Added "Additional Parameters Field" for Trakt RSS Feed (#1308)
Added Additional Parameters field similar to Indexers
2017-04-03 20:53:04 -04:00
geogolem
a9154559b8 Released icon is back 2017-03-29 10:07:09 +02:00
Leonardo Galli
0f2f2e4b32 Fixed spelling mistake 2017-03-28 09:51:31 +02:00
Leonardo Galli
6deefbb997 Fixed an error when searching for movies with no imdbid. 2017-03-28 09:34:49 +02:00
Leonardo Galli
43a71da0a7 Merge remote-tracking branch 'origin/develop' into develop 2017-03-27 17:07:38 +02:00
Leonardo Galli
499e46e10a Fixed error when downloading a movie. 2017-03-27 17:07:23 +02:00
Marcelo Castagna
3f013271c9 Fixed: DownloadStation api client for DSM 5.x. (#1259) 2017-03-26 23:57:29 +02:00
Leonardo Galli
529591bc18 Should fix covers not being local 2017-03-26 19:39:12 +02:00
Leonardo Galli
79307d3c25 Fixed only one movie appearing when list does not give us a tmdbid 2017-03-26 14:59:36 +02:00
Leonardo Galli
8f79563cf0 This should fix all imdbid problems with indexers. 2017-03-21 18:51:58 +01:00
Devin Buhl
0dc67419be Revert "Move up IMDB logic in ParsingService, should help with the mismatched movies"
This reverts commit 066c746e5f.
2017-03-21 18:29:27 +01:00
Devin Buhl
066c746e5f Move up IMDB logic in ParsingService, should help with the mismatched movies 2017-03-20 21:51:48 -04:00
Zach
31fcac5bd9 Clean up jsHint warnings (#1225) 2017-03-19 12:34:22 -04:00
thejacer87
1b29b89bf1 New movie search (#1212)
* add movie search empty template (#1149)

* hooked up new route in controller (#1149)
2017-03-18 00:53:09 -04:00
Devin Buhl
c593f4250d Fix pending release service, HDBits, also the release deduper. Clean up housekeeping (#1211)
* Fix HDbits

* Fix pending release service, also fix the deduper

* Clean up the cleanup'er (housekeeping)

* Revert "Clean up the cleanup'er (housekeeping)"

This reverts commit c03c13d924.

* Housekeeping updates, without breaking tests

* Fix last test
2017-03-18 00:29:18 -04:00
Devin Buhl
59c07cc5f3 Patch/onedr0p 3 16 17 (#1200)
* clear localStorage on radarr update.. ya mon

* Fix when movie folder is deleted from disk and keeps showing up as downloaded in radarr

* Clear all UI localStorage items on update, set pageSize to what it needs to be.
2017-03-16 18:43:06 -04:00
Devin Buhl
2b1023e768 Revert "Small changes to list sync (#1179)"
This reverts commit f10af08f95.
2017-03-14 23:27:36 -04:00
Devin Buhl
f10af08f95 Small changes to list sync (#1179) 2017-03-14 23:22:36 -04:00
Devin Buhl
18fcda5fd6 Patch/onedr0p 3 14 17 (#1171)
* Upstream patch for rtorrent

* Whoops goes PTP
2017-03-14 14:18:36 -04:00
Leonardo Galli
1ccfde334f Maybe fix PTP? Don't have an account, so cannot test.
We should realy add some ptp tests.
2017-03-14 17:45:19 +01:00
Leonardo Galli
421a191650 Fix for editing quality of movie files.
Fixes #1165
2017-03-14 16:26:39 +01:00
Devin Buhl
35e046bb87 Patch/onedr0p 3 13 17 (#1166)
* Update message when the person has no movies with helpful links

* Added ImdbId to the release info from AwesomeHD

* Add ImdbId to release info for PTP and HDBits

* ImdbId is required for HDBits

* Added some error handling in SkyHook for TMDb

* Remove un-needed imports

* DIsable movie search after list sync (causing issues)

* small change

* Fix HDBits tests
2017-03-13 22:34:25 -04:00
Devin Buhl
8ece7e8b4d Update ISSUE_TEMPLATE.md 2017-03-13 16:24:21 -04:00
Devin Buhl
fadc5f0099 Update ISSUE_TEMPLATE.md 2017-03-13 16:23:57 -04:00
Devin Buhl
680430737d Update ISSUE_TEMPLATE.md 2017-03-13 16:23:47 -04:00
Leonardo Galli
5d3750a295 Fix issue where 1080p Telesyncs get tagged as 1080p Blurays. 2017-03-13 18:22:51 +01:00
120 changed files with 7169 additions and 1201 deletions

View File

@@ -1,5 +1,9 @@
**Description:** **Description:**
Check first that your problem is not listed in our wiki section:
* https://github.com/Radarr/Radarr/wiki/Common-Problems
* https://github.com/Radarr/Radarr/wiki/FAQ
Provide a description of the feature request or bug here, the more details the better. Provide a description of the feature request or bug here, the more details the better.
Please also try to include the following if you are reporting a bug Please also try to include the following if you are reporting a bug

View File

@@ -1,5 +1,5 @@
<p align="center"> <p align="center">
<img src="/Logo/text256.png" alt="Radarr"> <img src="/Logo/text256.png" alt="Radarr">
</p> </p>
Radarr is an __independent__ fork of [Sonarr](https://github.com/Sonarr/Sonarr) reworked for automatically downloading movies via Usenet and BitTorrent. Radarr is an __independent__ fork of [Sonarr](https://github.com/Sonarr/Sonarr) reworked for automatically downloading movies via Usenet and BitTorrent.
@@ -72,14 +72,21 @@ Radarr is currently undergoing rapid development and pull requests are actively
* New PassThePopcorn Indexer * New PassThePopcorn Indexer
* QBittorrent, Deluge, rTorrent, Transmission and uTorrent download client (Other clients are coming) * QBittorrent, Deluge, rTorrent, Transmission and uTorrent download client (Other clients are coming)
* New TorrentPotato Indexer (Works well with [Jackett](https://github.com/Jackett/Jackett)) * New TorrentPotato Indexer (Works well with [Jackett](https://github.com/Jackett/Jackett))
* Scanning PreDB to know when a new release is available
* Importing movies from various online sources, such as IMDb Watchlists (A complete list can be found [here](https://github.com/Radarr/Radarr/issues/114))
* Full integration with Kodi, Plex (notification, library update)
* And a beautiful UI * And a beautiful UI
### Planned Features ### Planned Features
* Scanning PreDB to know when a new release is available * Downloading Metadata such as trailers or subtitles (\*)
* Fixing the other Indexers and download clients * Adding metadata such as posters and information for Kodi and others to use (\*)
* Importing movies from various online sources, such as IMDb Watchlists (A complete list can be found [here](https://github.com/Radarr/Radarr/issues/114)) * Dynamically renaming folders with quality info, etc. (\*)
* Full integration with Kodi, Plex (notification, library update, metadata) * Supporting custom folder structures, such as all movie files in one folder (\*)
* Supporting multiple editions per movies (waiting on The Movie Database to finish their implementation)
* Supporting collections of movies, such as James Bond
**Note:** All features marked with (\*) are set to be in the first release of Radarr.
#### [Feature Requests](http://feathub.com/Radarr/Radarr) #### [Feature Requests](http://feathub.com/Radarr/Radarr)

View File

@@ -27,6 +27,7 @@ test:
artifacts: artifacts:
- path: '_artifacts\*.zip' - path: '_artifacts\*.zip'
- path: '_artifacts\*.exe'
- path: '_artifacts\*.tar.gz' - path: '_artifacts\*.tar.gz'
cache: cache:
@@ -36,9 +37,17 @@ cache:
pull_requests: pull_requests:
do_not_increment_build_number: true do_not_increment_build_number: true
on_failure:
- ps: Get-ChildItem .\_artifacts\*.zip | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
- ps: Get-ChildItem .\_artifacts\*.exe | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
- ps: Get-ChildItem .\_artifacts\*.tar.gz | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
only_commits: only_commits:
files: files:
- src/ - src/
- osx/ - osx/
- gulp/ - gulp/
- logo/ - logo/
- setup/
- appveyor.yml
- build-appveyor.cake

View File

@@ -264,6 +264,13 @@ Task("ArtifactsWindows").Does(() => {
CopyDirectory(outputFolder, artifactsFolderWindows + "/Radarr"); CopyDirectory(outputFolder, artifactsFolderWindows + "/Radarr");
}); });
Task("ArtifactsWindowsInstaller").Does(() => {
InnoSetup("./setup/nzbdrone.iss", new InnoSetupSettings {
OutputDirectory = artifactsFolder,
ToolPath = "./setup/inno/ISCC.exe"
});
});
Task("ArtifactsLinux").Does(() => { Task("ArtifactsLinux").Does(() => {
CopyDirectory(outputFolderMono, artifactsFolderLinux + "/Radarr"); CopyDirectory(outputFolderMono, artifactsFolderLinux + "/Radarr");
}); });
@@ -293,6 +300,7 @@ Task("CompressArtifacts").Does(() => {
Task("Artifacts") Task("Artifacts")
.IsDependentOn("CleanArtifacts") .IsDependentOn("CleanArtifacts")
.IsDependentOn("ArtifactsWindows") .IsDependentOn("ArtifactsWindows")
.IsDependentOn("ArtifactsWindowsInstaller")
.IsDependentOn("ArtifactsLinux") .IsDependentOn("ArtifactsLinux")
.IsDependentOn("ArtifactsOsx") .IsDependentOn("ArtifactsOsx")
.IsDependentOn("ArtifactsOsxApp") .IsDependentOn("ArtifactsOsxApp")

View File

@@ -208,9 +208,9 @@ PackageTests()
find $sourceFolder -path $testSearchPattern -exec cp -r -u -T "{}" $testPackageFolder \; find $sourceFolder -path $testSearchPattern -exec cp -r -u -T "{}" $testPackageFolder \;
if [ $runtime = "dotnet" ] ; then if [ $runtime = "dotnet" ] ; then
$nuget install NUnit.ConsoleRunner -Version 3.2.0 -Output $testPackageFolder $nuget install NUnit.Runners -Version 3.2.1 -Output $testPackageFolder
else else
mono $nuget install NUnit.ConsoleRunner -Version 3.2.0 -Output $testPackageFolder mono $nuget install NUnit.Runners -Version 3.2.1 -Output $testPackageFolder
fi fi
cp $outputFolder/*.dll $testPackageFolder cp $outputFolder/*.dll $testPackageFolder

View File

@@ -23,6 +23,9 @@ if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
DAY="`date +%d`" DAY="`date +%d`"
else else
VERSION=$1 VERSION=$1
BRANCH=$2
BRANCH=${BRANCH#refs\/heads\/}
BRANCH=${BRANCH//\//-}
fi fi
outputFolder='./_output' outputFolder='./_output'
outputFolderMono='./_output_mono' outputFolderMono='./_output_mono'
@@ -34,35 +37,41 @@ rm $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr
chmod +x $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr2 chmod +x $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr2
mv $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr2 $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr >& error.log mv $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr2 $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr >& error.log
cp -r $outputFolder/ Radarr_Windows_$VERSION
cp -r $outputFolderMono/ Radarr_Mono_$VERSION
cp -r $outputFolderOsxApp/ Radarr_OSX_$VERSION
if [ $runtime = "dotnet" ] ; then if [ $runtime = "dotnet" ] ; then
./7za.exe a Radarr_Windows_$VERSION.zip ./Radarr_Windows_$VERSION/* ./7za.exe a Radarr_Windows_$VERSION.zip ./Radarr_Windows_$VERSION/*
./7za.exe a -ttar -so Radarr_Mono_$VERSION.tar ./Radarr_Mono_$VERSION/* | ./7za.exe a -si Radarr_Mono_$VERSION.tar.gz ./7za.exe a -ttar -so Radarr_Mono_$VERSION.tar ./Radarr_Mono_$VERSION/* | ./7za.exe a -si Radarr_Mono_$VERSION.tar.gz
./7za.exe a -ttar -so Radarr_OSX_$VERSION.tar ./_output_osx/* | ./7za.exe a -si Radarr_OSX_$VERSION.tar.gz ./7za.exe a -ttar -so Radarr_OSX_$VERSION.tar ./_output_osx/* | ./7za.exe a -si Radarr_OSX_$VERSION.tar.gz
./7za.exe a -ttar -so Radarr_OSX_App_$VERSION.tar ./_output_osx_app/* | ./7za.exe a -si Radarr_OSX_App_$VERSION.tar.gz ./7za.exe a -ttar -so Radarr_OSX_App_$VERSION.tar ./_output_osx_app/* | ./7za.exe a -si Radarr_OSX_App_$VERSION.tar.gz
else else
zip -r Radarr_Windows_$VERSION.zip Radarr_Windows_$VERSION/* >& /dev/null cp -r $outputFolder/ Radarr
zip -r Radarr_Mono_$VERSION.zip Radarr_Mono_$VERSION/* >& /dev/null #TODO update for tar.gz zip -r Radarr.$BRANCH.$VERSION.windows.zip Radarr
zip -r Radarr_OSX_$VERSION_App.zip Radarr_OSX_$VERSION/* >& /dev/null rm -rf Radarr
cp -r $outputFolderMono/ Radarr
tar -zcvf Radarr.$BRANCH.$VERSION.linux.tar.gz Radarr
rm -rf Radarr
cp -r $outputFolderOsx/ Radarr
tar -zcvf Radarr.$BRANCH.$VERSION.osx.tar.gz Radarr
rm -rf Radarr
#TODO update for tar.gz
cd _output_osx_app/
zip -r ../Radarr.$BRANCH.$VERSION.osx-app.zip *
fi fi
ftp -n ftp.leonardogalli.ch << END_SCRIPT # ftp -n ftp.leonardogalli.ch << END_SCRIPT
passive # passive
quote USER $FTP_USER # quote USER $FTP_USER
quote PASS $FTP_PASS # quote PASS $FTP_PASS
mkdir builds # mkdir builds
cd builds # cd builds
mkdir $YEAR # mkdir $YEAR
cd $YEAR # cd $YEAR
mkdir $MONTH # mkdir $MONTH
cd $MONTH # cd $MONTH
mkdir $DAY # mkdir $DAY
cd $DAY # cd $DAY
binary # binary
put Radarr_Windows_$VERSION.zip # put Radarr_Windows_$VERSION.zip
put Radarr_Mono_$VERSION.zip # put Radarr_Mono_$VERSION.zip
put Radarr_OSX_$VERSION.zip # put Radarr_OSX_$VERSION.zip
quit # quit
END_SCRIPT # END_SCRIPT

View File

@@ -1,35 +1,35 @@
; Script generated by the Inno Setup Script Wizard. ; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define AppName "Sonarr" #define AppName "Radarr"
#define AppPublisher "Team Sonarr" #define AppPublisher "Team Radarr"
#define AppURL "https://sonarr.tv/" #define AppURL "https://radarr.video/"
#define ForumsURL "https://forums.sonarr.tv/" #define ForumsURL "https://github.com/Radarr/Radarr/issues"
#define AppExeName "NzbDrone.exe" #define AppExeName "Radarr.exe"
#define BuildNumber "2.0" #define BuildNumber "2.0"
#define BuildNumber GetEnv('BUILD_NUMBER') #define BuildVersion GetEnv('APPVEYOR_BUILD_VERSION')
#define BranchName GetEnv('branch') #define BranchName GetEnv('APPVEYOR_REPO_BRANCH')
[Setup] [Setup]
; NOTE: The value of AppId uniquely identifies this application. ; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications. ; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{56C1065D-3523-4025-B76D-6F73F67F7F71} AppId={{56C1065D-3523-4025-B76D-6F73F67F7F82}
AppName={#AppName} AppName={#AppName}
AppVersion=2.0 AppVersion=0.2
AppPublisher={#AppPublisher} AppPublisher={#AppPublisher}
AppPublisherURL={#AppURL} AppPublisherURL={#AppURL}
AppSupportURL={#ForumsURL} AppSupportURL={#ForumsURL}
AppUpdatesURL={#AppURL} AppUpdatesURL={#AppURL}
DefaultDirName={commonappdata}\NzbDrone\bin DefaultDirName={commonappdata}\Radarr\bin
DisableDirPage=yes DisableDirPage=yes
DefaultGroupName={#AppName} DefaultGroupName={#AppName}
DisableProgramGroupPage=yes DisableProgramGroupPage=yes
OutputBaseFilename=NzbDrone.{#BranchName}.{#BuildNumber} OutputBaseFilename=Radarr.{#BranchName}.{#BuildVersion}.installer
SolidCompression=yes SolidCompression=yes
AppCopyright=Creative Commons 3.0 License AppCopyright=Creative Commons 3.0 License
AllowUNCPath=False AllowUNCPath=False
UninstallDisplayIcon={app}\NzbDrone.exe UninstallDisplayIcon={app}\Radarr.exe
DisableReadyPage=True DisableReadyPage=True
CompressionThreads=2 CompressionThreads=2
Compression=lzma2/normal Compression=lzma2/normal
@@ -44,7 +44,7 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "windowsService"; Description: "Install as a Windows Service" Name: "windowsService"; Description: "Install as a Windows Service"
[Files] [Files]
Source: "..\_output\NzbDrone.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "..\_output\Radarr.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\_output\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "..\_output\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
@@ -53,8 +53,8 @@ Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Parameters: "/icon"
Name: "{commondesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Parameters: "/icon" Name: "{commondesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Parameters: "/icon"
[Run] [Run]
Filename: "{app}\nzbdrone.console.exe"; Parameters: "/u"; Flags: waituntilterminated; Filename: "{app}\radarr.console.exe"; Parameters: "/u"; Flags: waituntilterminated;
Filename: "{app}\nzbdrone.console.exe"; Parameters: "/i"; Flags: waituntilterminated; Tasks: windowsService Filename: "{app}\radarr.console.exe"; Parameters: "/i"; Flags: waituntilterminated; Tasks: windowsService
[UninstallRun] [UninstallRun]
Filename: "{app}\nzbdrone.console.exe"; Parameters: "/u"; Flags: waituntilterminated skipifdoesntexist Filename: "{app}\radarr.console.exe"; Parameters: "/u"; Flags: waituntilterminated skipifdoesntexist

View File

@@ -8,7 +8,10 @@ namespace NzbDrone.Api.Config
public int MinimumAge { get; set; } public int MinimumAge { get; set; }
public int Retention { get; set; } public int Retention { get; set; }
public int RssSyncInterval { get; set; } public int RssSyncInterval { get; set; }
public int AvailabilityDelay { get; set; } public bool PreferIndexerFlags { get; set; }
public int AvailabilityDelay { get; set; }
public bool AllowHardcodedSubs { get; set; }
public string WhitelistedHardcodedSubs { get; set; }
} }
public static class IndexerConfigResourceMapper public static class IndexerConfigResourceMapper
@@ -20,7 +23,11 @@ namespace NzbDrone.Api.Config
MinimumAge = model.MinimumAge, MinimumAge = model.MinimumAge,
Retention = model.Retention, Retention = model.Retention,
RssSyncInterval = model.RssSyncInterval, RssSyncInterval = model.RssSyncInterval,
AvailabilityDelay = model.AvailabilityDelay, PreferIndexerFlags = model.PreferIndexerFlags,
AvailabilityDelay = model.AvailabilityDelay,
AllowHardcodedSubs = model.AllowHardcodedSubs,
WhitelistedHardcodedSubs = model.WhitelistedHardcodedSubs,
}; };
} }
} }

View File

@@ -46,6 +46,7 @@ namespace NzbDrone.Api.Indexers
public bool DownloadAllowed { get; set; } public bool DownloadAllowed { get; set; }
public int ReleaseWeight { get; set; } public int ReleaseWeight { get; set; }
public IEnumerable<string> IndexerFlags { get; set; }
public string MagnetUrl { get; set; } public string MagnetUrl { get; set; }
public string InfoHash { get; set; } public string InfoHash { get; set; }
@@ -132,7 +133,7 @@ namespace NzbDrone.Api.Indexers
Seeders = torrentInfo.Seeders, Seeders = torrentInfo.Seeders,
Leechers = (torrentInfo.Peers.HasValue && torrentInfo.Seeders.HasValue) ? (torrentInfo.Peers.Value - torrentInfo.Seeders.Value) : (int?)null, Leechers = (torrentInfo.Peers.HasValue && torrentInfo.Seeders.HasValue) ? (torrentInfo.Peers.Value - torrentInfo.Seeders.Value) : (int?)null,
Protocol = releaseInfo.DownloadProtocol, Protocol = releaseInfo.DownloadProtocol,
IndexerFlags = torrentInfo.IndexerFlags.ToString().Split(new string[] { ", " }, StringSplitOptions.None),
Edition = parsedMovieInfo.Edition, Edition = parsedMovieInfo.Edition,
IsDaily = false, IsDaily = false,

View File

@@ -105,7 +105,7 @@ namespace NzbDrone.Api.Queue
throw new NotFoundException(); throw new NotFoundException();
} }
_downloadService.DownloadReport(pendingRelease.RemoteEpisode); _downloadService.DownloadReport(pendingRelease.RemoteMovie);
return resource.AsResponse(); return resource.AsResponse();
} }

View File

@@ -118,7 +118,7 @@ namespace NzbDrone.Api.Movie
pagingSpec.FilterExpression = _moviesService.ConstructFilterExpression(pagingResource.FilterKey, pagingResource.FilterValue, pagingResource.FilterType); pagingSpec.FilterExpression = _moviesService.ConstructFilterExpression(pagingResource.FilterKey, pagingResource.FilterValue, pagingResource.FilterType);
return ApplyToPage(_moviesService.Paged, pagingSpec, MovieResourceMapper.ToResource); return ApplyToPage(_moviesService.Paged, pagingSpec, MapToResource);
} }
protected MovieResource MapToResource(Core.Tv.Movie movies) protected MovieResource MapToResource(Core.Tv.Movie movies)
@@ -213,7 +213,7 @@ namespace NzbDrone.Api.Movie
private void LinkMovieStatistics(MovieResource resource, MovieStatistics moviesStatistics) private void LinkMovieStatistics(MovieResource resource, MovieStatistics moviesStatistics)
{ {
resource.SizeOnDisk = moviesStatistics.SizeOnDisk; //resource.SizeOnDisk = 0;//TODO: incorporate movie statistics moviesStatistics.SizeOnDisk;
} }
private void PopulateAlternateTitles(List<MovieResource> resources) private void PopulateAlternateTitles(List<MovieResource> resources)

View File

@@ -120,7 +120,7 @@ namespace NzbDrone.Api.Movie
//TotalEpisodeCount //TotalEpisodeCount
//EpisodeCount //EpisodeCount
//EpisodeFileCount //EpisodeFileCount
//SizeOnDisk SizeOnDisk = size,
Status = model.Status, Status = model.Status,
Overview = model.Overview, Overview = model.Overview,
//NextAiring //NextAiring
@@ -138,7 +138,7 @@ namespace NzbDrone.Api.Movie
IsAvailable = model.IsAvailable(), IsAvailable = model.IsAvailable(),
FolderName = model.FolderName(), FolderName = model.FolderName(),
SizeOnDisk = size, //SizeOnDisk = size,
Runtime = model.Runtime, Runtime = model.Runtime,
LastInfoSync = model.LastInfoSync, LastInfoSync = model.LastInfoSync,

View File

@@ -29,7 +29,7 @@ namespace NzbDrone.Common.Test.DiskTests
public void should_be_able_to_check_space_on_ramdrive() public void should_be_able_to_check_space_on_ramdrive()
{ {
MonoOnly(); MonoOnly();
Subject.GetAvailableSpace("/run/").Should().NotBe(0); Subject.GetAvailableSpace("/").Should().NotBe(0);
} }
[Test] [Test]

View File

@@ -20,7 +20,6 @@ namespace NzbDrone.Common.Test
} }
[TestCase("")] [TestCase("")]
[TestCase("http://")]
public void DownloadString_should_throw_on_error(string url) public void DownloadString_should_throw_on_error(string url)
{ {
Assert.Throws<ArgumentException>(() => Subject.DownloadString(url)); Assert.Throws<ArgumentException>(() => Subject.DownloadString(url));

View File

@@ -4,6 +4,7 @@
{ {
public static readonly HttpAccept Rss = new HttpAccept("application/rss+xml, text/rss+xml, application/xml, text/xml"); public static readonly HttpAccept Rss = new HttpAccept("application/rss+xml, text/rss+xml, application/xml, text/xml");
public static readonly HttpAccept Json = new HttpAccept("application/json"); public static readonly HttpAccept Json = new HttpAccept("application/json");
public static readonly HttpAccept JsonCharset = new HttpAccept("application/json;charset=utf-8");
public static readonly HttpAccept Html = new HttpAccept("text/html"); public static readonly HttpAccept Html = new HttpAccept("text/html");
public string Value { get; private set; } public string Value { get; private set; }

View File

@@ -20,6 +20,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private RemoteEpisode parseResultMulti; private RemoteEpisode parseResultMulti;
private RemoteEpisode parseResultSingle; private RemoteEpisode parseResultSingle;
private Series series; private Series series;
private Movie movie;
private RemoteMovie remoteMovie;
private QualityDefinition qualityType; private QualityDefinition qualityType;
[SetUp] [SetUp]
@@ -28,6 +30,16 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
series = Builder<Series>.CreateNew() series = Builder<Series>.CreateNew()
.Build(); .Build();
movie = Builder<Movie>.CreateNew().Build();
remoteMovie = new RemoteMovie
{
Movie = movie,
Release = new ReleaseInfo(),
ParsedMovieInfo = new ParsedMovieInfo { Quality = new QualityModel(Quality.SDTV, new Revision(version: 2)) },
};
parseResultMultiSet = new RemoteEpisode parseResultMultiSet = new RemoteEpisode
{ {
Series = series, Series = series,
@@ -216,5 +228,17 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.IsSatisfiedBy(parseResultSingle, null).Accepted.Should().BeTrue(); Subject.IsSatisfiedBy(parseResultSingle, null).Accepted.Should().BeTrue();
} }
[Test]
public void should_use_110_minutes_if_runtime_is_0()
{
movie.Runtime = 0;
remoteMovie.Movie = movie;
remoteMovie.Release.Size = 1095.Megabytes();
Subject.IsSatisfiedBy(remoteMovie, null).Accepted.Should().Be(true);
remoteMovie.Release.Size = 1105.Megabytes();
Subject.IsSatisfiedBy(remoteMovie, null).Accepted.Should().Be(false);
}
} }
} }

View File

@@ -275,7 +275,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
{ "default_destination", _defaultDestination }, { "default_destination", _defaultDestination },
}; };
Mocker.GetMock<IDownloadStationProxy>() Mocker.GetMock<IDownloadStationInfoProxy>()
.Setup(v => v.GetConfig(It.IsAny<DownloadStationSettings>())) .Setup(v => v.GetConfig(It.IsAny<DownloadStationSettings>()))
.Returns(_downloadStationConfigItems); .Returns(_downloadStationConfigItems);
} }
@@ -311,7 +311,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
torrents = new List<DownloadStationTask>(); torrents = new List<DownloadStationTask>();
} }
Mocker.GetMock<IDownloadStationProxy>() Mocker.GetMock<IDownloadStationTaskProxy>()
.Setup(s => s.GetTasks(It.IsAny<DownloadStationSettings>())) .Setup(s => s.GetTasks(It.IsAny<DownloadStationSettings>()))
.Returns(torrents); .Returns(torrents);
} }
@@ -330,11 +330,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
.Setup(s => s.Get(It.IsAny<HttpRequest>())) .Setup(s => s.Get(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[1000])); .Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[1000]));
Mocker.GetMock<IDownloadStationProxy>() Mocker.GetMock<IDownloadStationTaskProxy>()
.Setup(s => s.AddTaskFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DownloadStationSettings>())) .Setup(s => s.AddTaskFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DownloadStationSettings>()))
.Callback(PrepareClientToReturnQueuedItem); .Callback(PrepareClientToReturnQueuedItem);
Mocker.GetMock<IDownloadStationProxy>() Mocker.GetMock<IDownloadStationTaskProxy>()
.Setup(s => s.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DownloadStationSettings>())) .Setup(s => s.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DownloadStationSettings>()))
.Callback(PrepareClientToReturnQueuedItem); .Callback(PrepareClientToReturnQueuedItem);
} }
@@ -352,7 +352,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
{ {
var tasks = new List<DownloadStationTask>() { _queued, _completed, _failed, _downloading, _seeding }; var tasks = new List<DownloadStationTask>() { _queued, _completed, _failed, _downloading, _seeding };
Mocker.GetMock<IDownloadStationProxy>() Mocker.GetMock<IDownloadStationTaskProxy>()
.Setup(d => d.GetTasks(_settings)) .Setup(d => d.GetTasks(_settings))
.Returns(tasks); .Returns(tasks);
@@ -372,7 +372,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
id.Should().NotBeNullOrEmpty(); id.Should().NotBeNullOrEmpty();
Mocker.GetMock<IDownloadStationProxy>() Mocker.GetMock<IDownloadStationTaskProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), _tvDirectory, It.IsAny<DownloadStationSettings>()), Times.Once()); .Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), _tvDirectory, It.IsAny<DownloadStationSettings>()), Times.Once());
} }
@@ -389,7 +389,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
id.Should().NotBeNullOrEmpty(); id.Should().NotBeNullOrEmpty();
Mocker.GetMock<IDownloadStationProxy>() Mocker.GetMock<IDownloadStationTaskProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), $"{_defaultDestination}/{_category}", It.IsAny<DownloadStationSettings>()), Times.Once()); .Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), $"{_defaultDestination}/{_category}", It.IsAny<DownloadStationSettings>()), Times.Once());
} }
@@ -405,7 +405,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
id.Should().NotBeNullOrEmpty(); id.Should().NotBeNullOrEmpty();
Mocker.GetMock<IDownloadStationProxy>() Mocker.GetMock<IDownloadStationTaskProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, It.IsAny<DownloadStationSettings>()), Times.Once()); .Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, It.IsAny<DownloadStationSettings>()), Times.Once());
} }
@@ -482,7 +482,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.Download(remoteEpisode)); Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.Download(remoteEpisode));
Mocker.GetMock<IDownloadStationProxy>() Mocker.GetMock<IDownloadStationTaskProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, _settings), Times.Never()); .Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, _settings), Times.Never());
} }

View File

@@ -177,7 +177,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
{ "default_destination", _defaultDestination }, { "default_destination", _defaultDestination },
}; };
Mocker.GetMock<IDownloadStationProxy>() Mocker.GetMock<IDownloadStationInfoProxy>()
.Setup(v => v.GetConfig(It.IsAny<DownloadStationSettings>())) .Setup(v => v.GetConfig(It.IsAny<DownloadStationSettings>()))
.Returns(_downloadStationConfigItems); .Returns(_downloadStationConfigItems);
} }
@@ -213,7 +213,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
nzbs = new List<DownloadStationTask>(); nzbs = new List<DownloadStationTask>();
} }
Mocker.GetMock<IDownloadStationProxy>() Mocker.GetMock<IDownloadStationTaskProxy>()
.Setup(s => s.GetTasks(It.IsAny<DownloadStationSettings>())) .Setup(s => s.GetTasks(It.IsAny<DownloadStationSettings>()))
.Returns(nzbs); .Returns(nzbs);
} }
@@ -233,7 +233,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[1000])); .Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[1000]));
*/ */
Mocker.GetMock<IDownloadStationProxy>() Mocker.GetMock<IDownloadStationTaskProxy>()
.Setup(s => s.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DownloadStationSettings>())) .Setup(s => s.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DownloadStationSettings>()))
.Callback(PrepareClientToReturnQueuedItem); .Callback(PrepareClientToReturnQueuedItem);
} }
@@ -242,7 +242,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
{ {
var tasks = new List<DownloadStationTask>() { _queued, _completed, _failed, _downloading, _seeding }; var tasks = new List<DownloadStationTask>() { _queued, _completed, _failed, _downloading, _seeding };
Mocker.GetMock<IDownloadStationProxy>() Mocker.GetMock<IDownloadStationTaskProxy>()
.Setup(d => d.GetTasks(_settings)) .Setup(d => d.GetTasks(_settings))
.Returns(tasks); .Returns(tasks);
} }
@@ -260,7 +260,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
id.Should().NotBeNullOrEmpty(); id.Should().NotBeNullOrEmpty();
Mocker.GetMock<IDownloadStationProxy>() Mocker.GetMock<IDownloadStationTaskProxy>()
.Verify(v => v.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), _tvDirectory, It.IsAny<DownloadStationSettings>()), Times.Once()); .Verify(v => v.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), _tvDirectory, It.IsAny<DownloadStationSettings>()), Times.Once());
} }
@@ -277,7 +277,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
id.Should().NotBeNullOrEmpty(); id.Should().NotBeNullOrEmpty();
Mocker.GetMock<IDownloadStationProxy>() Mocker.GetMock<IDownloadStationTaskProxy>()
.Verify(v => v.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), $"{_defaultDestination}/{_category}", It.IsAny<DownloadStationSettings>()), Times.Once()); .Verify(v => v.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), $"{_defaultDestination}/{_category}", It.IsAny<DownloadStationSettings>()), Times.Once());
} }
@@ -293,7 +293,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
id.Should().NotBeNullOrEmpty(); id.Should().NotBeNullOrEmpty();
Mocker.GetMock<IDownloadStationProxy>() Mocker.GetMock<IDownloadStationTaskProxy>()
.Verify(v => v.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), null, It.IsAny<DownloadStationSettings>()), Times.Once()); .Verify(v => v.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), null, It.IsAny<DownloadStationSettings>()), Times.Once());
} }
@@ -370,7 +370,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.Download(remoteEpisode)); Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.Download(remoteEpisode));
Mocker.GetMock<IDownloadStationProxy>() Mocker.GetMock<IDownloadStationTaskProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, _settings), Times.Never()); .Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, _settings), Times.Never());
} }

View File

@@ -25,26 +25,26 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.RTorrentTests
}; };
_downloading = new RTorrentTorrent _downloading = new RTorrentTorrent
{ {
Hash = "HASH", Hash = "HASH",
IsFinished = false, IsFinished = false,
IsOpen = true, IsOpen = true,
IsActive = true, IsActive = true,
Name = _title, Name = _title,
TotalSize = 1000, TotalSize = 1000,
RemainingSize = 500, RemainingSize = 500,
Path = "somepath" Path = "somepath"
}; };
_completed = new RTorrentTorrent _completed = new RTorrentTorrent
{ {
Hash = "HASH", Hash = "HASH",
IsFinished = true, IsFinished = true,
Name = _title, Name = _title,
TotalSize = 1000, TotalSize = 1000,
RemainingSize = 0, RemainingSize = 0,
Path = "somepath" Path = "somepath"
}; };
Mocker.GetMock<ITorrentFileInfoReader>() Mocker.GetMock<ITorrentFileInfoReader>()
.Setup(s => s.GetHashFromTorrentFile(It.IsAny<byte[]>())) .Setup(s => s.GetHashFromTorrentFile(It.IsAny<byte[]>()))
@@ -54,11 +54,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.RTorrentTests
protected void GivenSuccessfulDownload() protected void GivenSuccessfulDownload()
{ {
Mocker.GetMock<IRTorrentProxy>() Mocker.GetMock<IRTorrentProxy>()
.Setup(s => s.AddTorrentFromUrl(It.IsAny<string>(), It.IsAny<RTorrentSettings>())) .Setup(s => s.AddTorrentFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<RTorrentPriority>(), It.IsAny<string>(), It.IsAny<RTorrentSettings>()))
.Callback(PrepareClientToReturnCompletedItem); .Callback(PrepareClientToReturnCompletedItem);
Mocker.GetMock<IRTorrentProxy>() Mocker.GetMock<IRTorrentProxy>()
.Setup(s => s.AddTorrentFromFile(It.IsAny<string>(), It.IsAny<byte[]>(), It.IsAny<RTorrentSettings>())) .Setup(s => s.AddTorrentFromFile(It.IsAny<string>(), It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<RTorrentPriority>(), It.IsAny<string>(), It.IsAny<RTorrentSettings>()))
.Callback(PrepareClientToReturnCompletedItem); .Callback(PrepareClientToReturnCompletedItem);
@@ -116,11 +116,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.RTorrentTests
{ {
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteMovie = CreateRemoteMovie(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteMovie); var id = Subject.Download(remoteEpisode);
id.Should().NotBeNullOrEmpty(); id.Should().NotBeNullOrEmpty();
} }
} }
} }

View File

@@ -23,6 +23,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
private SabnzbdHistory _failed; private SabnzbdHistory _failed;
private SabnzbdHistory _completed; private SabnzbdHistory _completed;
private SabnzbdConfig _config; private SabnzbdConfig _config;
private SabnzbdFullStatus _fullStatus;
[SetUp] [SetUp]
public void Setup() public void Setup()
@@ -65,7 +66,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
{ {
Status = SabnzbdDownloadStatus.Failed, Status = SabnzbdDownloadStatus.Failed,
Size = 1000, Size = 1000,
Category = "tv", Category = "tv",
Id = "sabnzbd_nzb12345", Id = "sabnzbd_nzb12345",
Title = "Droned.1998.1080p.WEB-DL-DRONE" Title = "Droned.1998.1080p.WEB-DL-DRONE"
} }
@@ -80,7 +81,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
{ {
Status = SabnzbdDownloadStatus.Completed, Status = SabnzbdDownloadStatus.Completed,
Size = 1000, Size = 1000,
Category = "tv", Category = "tv",
Id = "sabnzbd_nzb12345", Id = "sabnzbd_nzb12345",
Title = "Droned.1998.1080p.WEB-DL-DRONE", Title = "Droned.1998.1080p.WEB-DL-DRONE",
Storage = "/remote/mount/vv/Droned.1998.1080p.WEB-DL-DRONE" Storage = "/remote/mount/vv/Droned.1998.1080p.WEB-DL-DRONE"
@@ -100,9 +101,29 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
} }
}; };
Mocker.GetMock<ISabnzbdProxy>()
.Setup(v => v.GetVersion(It.IsAny<SabnzbdSettings>()))
.Returns("1.2.3");
Mocker.GetMock<ISabnzbdProxy>() Mocker.GetMock<ISabnzbdProxy>()
.Setup(s => s.GetConfig(It.IsAny<SabnzbdSettings>())) .Setup(s => s.GetConfig(It.IsAny<SabnzbdSettings>()))
.Returns(_config); .Returns(_config);
_fullStatus = new SabnzbdFullStatus
{
CompleteDir = @"Y:\nzbget\root\complete".AsOsAgnostic()
};
Mocker.GetMock<ISabnzbdProxy>()
.Setup(s => s.GetFullStatus(It.IsAny<SabnzbdSettings>()))
.Returns(_fullStatus);
}
protected void GivenVersion(string version)
{
Mocker.GetMock<ISabnzbdProxy>()
.Setup(s => s.GetVersion(It.IsAny<SabnzbdSettings>()))
.Returns(version);
} }
protected void GivenFailedDownload() protected void GivenFailedDownload()
@@ -166,7 +187,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
GivenQueue(_queued); GivenQueue(_queued);
GivenHistory(null); GivenHistory(null);
var result = Subject.GetItems().Single(); var result = Subject.GetItems().Single();
VerifyQueued(result); VerifyQueued(result);
@@ -387,23 +408,46 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
result.OutputPath.Should().Be(@"C:\sorted\somewhere\asdfasdf\asdfasdf.mkv".AsOsAgnostic()); result.OutputPath.Should().Be(@"C:\sorted\somewhere\asdfasdf\asdfasdf.mkv".AsOsAgnostic());
} }
[TestCase(@"Y:\nzbget\root", @"completed\downloads", @"vv", @"Y:\nzbget\root\completed\downloads\vv")] [TestCase(@"Y:\nzbget\root", @"completed\downloads", @"vv", @"Y:\nzbget\root\completed\downloads", @"Y:\nzbget\root\completed\downloads\vv")]
[TestCase(@"Y:\nzbget\root", @"completed", @"vv", @"Y:\nzbget\root\completed\vv")] [TestCase(@"Y:\nzbget\root", @"completed", @"vv", @"Y:\nzbget\root\completed", @"Y:\nzbget\root\completed\vv")]
[TestCase(@"/nzbget/root", @"completed/downloads", @"vv", @"/nzbget/root/completed/downloads/vv")] [TestCase(@"/nzbget/root", @"completed/downloads", @"vv", @"/nzbget/root/completed/downloads", @"/nzbget/root/completed/downloads/vv")]
[TestCase(@"/nzbget/root", @"completed", @"vv", @"/nzbget/root/completed/vv")] [TestCase(@"/nzbget/root", @"completed", @"vv", @"/nzbget/root/completed", @"/nzbget/root/completed/vv")]
public void should_return_status_with_outputdir(string rootFolder, string completeDir, string categoryDir, string expectedDir) public void should_return_status_with_outputdir_for_version_lt_2(string rootFolder, string completeDir, string categoryDir, string fullCompleteDir, string fullCategoryDir)
{ {
_fullStatus.CompleteDir = null;
_queued.DefaultRootFolder = rootFolder; _queued.DefaultRootFolder = rootFolder;
_config.Misc.complete_dir = completeDir; _config.Misc.complete_dir = completeDir;
_config.Categories.First().Dir = categoryDir; _config.Categories.First().Dir = categoryDir;
GivenVersion("1.2.1");
GivenQueue(null); GivenQueue(null);
var result = Subject.GetStatus(); var result = Subject.GetStatus();
result.IsLocalhost.Should().BeTrue(); result.IsLocalhost.Should().BeTrue();
result.OutputRootFolders.Should().NotBeNull(); result.OutputRootFolders.Should().NotBeNull();
result.OutputRootFolders.First().Should().Be(expectedDir); result.OutputRootFolders.First().Should().Be(fullCategoryDir);
}
[TestCase(@"Y:\nzbget\root", @"completed\downloads", @"vv", @"Y:\nzbget\root\completed\downloads", @"Y:\nzbget\root\completed\downloads\vv")]
[TestCase(@"Y:\nzbget\root", @"completed", @"vv", @"Y:\nzbget\root\completed", @"Y:\nzbget\root\completed\vv")]
[TestCase(@"/nzbget/root", @"completed/downloads", @"vv", @"/nzbget/root/completed/downloads", @"/nzbget/root/completed/downloads/vv")]
[TestCase(@"/nzbget/root", @"completed", @"vv", @"/nzbget/root/completed", @"/nzbget/root/completed/vv")]
public void should_return_status_with_outputdir_for_version_gte_2(string rootFolder, string completeDir, string categoryDir, string fullCompleteDir, string fullCategoryDir)
{
_fullStatus.CompleteDir = fullCompleteDir;
_queued.DefaultRootFolder = null;
_config.Misc.complete_dir = completeDir;
_config.Categories.First().Dir = categoryDir;
GivenVersion("2.0.0beta1");
GivenQueue(null);
var result = Subject.GetStatus();
result.IsLocalhost.Should().BeTrue();
result.OutputRootFolders.Should().NotBeNull();
result.OutputRootFolders.First().Should().Be(fullCategoryDir);
} }
[Test] [Test]
@@ -451,5 +495,73 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
result.IsValid.Should().BeTrue(); result.IsValid.Should().BeTrue();
result.HasWarnings.Should().BeTrue(); result.HasWarnings.Should().BeTrue();
} }
[Test]
public void should_test_success_if_tv_sorting_disabled()
{
_config.Misc.enable_tv_sorting = false;
_config.Misc.tv_categories = null;
var result = new NzbDroneValidationResult(Subject.Test());
result.IsValid.Should().BeTrue();
}
[Test]
public void should_test_failed_if_tv_sorting_null()
{
_config.Misc.enable_tv_sorting = true;
_config.Misc.tv_categories = null;
var result = new NzbDroneValidationResult(Subject.Test());
result.IsValid.Should().BeFalse();
}
[Test]
public void should_test_failed_if_tv_sorting_empty()
{
_config.Misc.enable_tv_sorting = true;
_config.Misc.tv_categories = new string[0];
var result = new NzbDroneValidationResult(Subject.Test());
result.IsValid.Should().BeFalse();
}
[Test]
public void should_test_success_if_tv_sorting_contains_different_category()
{
_config.Misc.enable_tv_sorting = true;
_config.Misc.tv_categories = new[] { "tv-custom" };
var result = new NzbDroneValidationResult(Subject.Test());
result.IsValid.Should().BeTrue();
}
[Test]
public void should_test_failed_if_tv_sorting_contains_category()
{
_config.Misc.enable_tv_sorting = true;
_config.Misc.tv_categories = new[] { "tv" };
var result = new NzbDroneValidationResult(Subject.Test());
result.IsValid.Should().BeFalse();
}
[Test]
public void should_test_failed_if_tv_sorting_default_category()
{
Subject.Definition.Settings.As<SabnzbdSettings>().TvCategory = null;
_config.Misc.enable_tv_sorting = true;
_config.Misc.tv_categories = new[] { "Default" };
var result = new NzbDroneValidationResult(Subject.Test());
result.IsValid.Should().BeFalse();
}
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,57 +1,63 @@
{ {
"status": 0, "status": 0,
"data": [ "data": [
{ {
"id": 257142, "id": 257142,
"hash": "EABC50AEF9F53CEDED84ADF14144D3368E586F3A", "hash": "EABC50AEF9F53CEDED84ADF14144D3368E586F3A",
"leechers": 1, "leechers": 1,
"seeders": 46, "seeders": 46,
"name": "Supernatural S10E17 1080p WEB-DL DD5.1 H.264-ECI", "name": "Supernatural S10E17 1080p WEB-DL DD5.1 H.264-ECI",
"times_completed": 49, "times_completed": 49,
"size": 1718009717, "size": 1718009717,
"utadded": 1428179446, "utadded": 1428179446,
"added": "2015-04-04T20:30:46+0000", "added": "2015-04-04T20:30:46+0000",
"comments": 0, "comments": 0,
"numfiles": 1, "numfiles": 1,
"filename": "Supernatural.S10E17.1080p.WEB-DL.DD5.1.H.264-ECI.torrent", "filename": "Supernatural.S10E17.1080p.WEB-DL.DD5.1.H.264-ECI.torrent",
"freeleech": "no", "freeleech": "no",
"type_category": 2, "type_category": 2,
"type_codec": 1, "type_codec": 1,
"type_medium": 6, "type_medium": 6,
"type_origin": 0, "type_origin": 0,
"username": "abc", "username": "abc",
"owner": 1107944, "owner": 1107944,
"tvdb": { "tvdb": {
"id": 78901, "id": 78901,
"season": 10, "season": 10,
"episode": 17 "episode": 17
}
}, },
{ "imdb": {
"id": 257140, "id": 78901
"hash": "BE3BA5396B9A30544353B55FDD89EDE46C8FB72A",
"leechers": 0,
"seeders": 18,
"name": "Scandal S04E18 1080p WEB-DL DD5.1 H.264-ECI",
"times_completed": 19,
"size": 1789106197,
"utadded": 1428179128,
"added": "2015-04-04T20:25:28+0000",
"comments": 0,
"numfiles": 1,
"filename": "Scandal.2012.S04E18.1080p.WEB-DL.DD5.1.H.264-ECI.torrent",
"freeleech": "no",
"type_category": 2,
"type_codec": 1,
"type_medium": 6,
"type_origin": 0,
"username": "abc",
"owner": 1107944,
"tvdb": {
"id": 248841,
"season": 4,
"episode": 18
}
} }
},
{
"id": 257140,
"hash": "BE3BA5396B9A30544353B55FDD89EDE46C8FB72A",
"leechers": 0,
"seeders": 18,
"name": "Scandal S04E18 1080p WEB-DL DD5.1 H.264-ECI",
"times_completed": 19,
"size": 1789106197,
"utadded": 1428179128,
"added": "2015-04-04T20:25:28+0000",
"comments": 0,
"numfiles": 1,
"filename": "Scandal.2012.S04E18.1080p.WEB-DL.DD5.1.H.264-ECI.torrent",
"freeleech": "no",
"type_category": 2,
"type_codec": 1,
"type_medium": 6,
"type_origin": 0,
"username": "abc",
"owner": 1107944,
"tvdb": {
"id": 248841,
"season": 4,
"episode": 18
},
"imdb": {
"id": 78901
}
}
] ]
} }

View File

@@ -1,57 +1,63 @@
{ {
"status": 0, "status": 0,
"data": [ "data": [
{ {
"id": "257142", "id": "257142",
"hash": "EABC50AEF9F53CEDED84ADF14144D3368E586F3A", "hash": "EABC50AEF9F53CEDED84ADF14144D3368E586F3A",
"leechers": 1, "leechers": 1,
"seeders": 46, "seeders": 46,
"name": "Supernatural S10E17 1080p WEB-DL DD5.1 H.264-ECI", "name": "Supernatural S10E17 1080p WEB-DL DD5.1 H.264-ECI",
"times_completed": 49, "times_completed": 49,
"size": 1718009717, "size": 1718009717,
"utadded": 1428179446, "utadded": 1428179446,
"added": "2015-04-04T20:30:46+0000", "added": "2015-04-04T20:30:46+0000",
"comments": 0, "comments": 0,
"numfiles": 1, "numfiles": 1,
"filename": "Supernatural.S10E17.1080p.WEB-DL.DD5.1.H.264-ECI.torrent", "filename": "Supernatural.S10E17.1080p.WEB-DL.DD5.1.H.264-ECI.torrent",
"freeleech": "no", "freeleech": "no",
"type_category": 2, "type_category": 2,
"type_codec": 1, "type_codec": 1,
"type_medium": 6, "type_medium": 6,
"type_origin": 0, "type_origin": 0,
"username": "abc", "username": "abc",
"owner": 1107944, "owner": 1107944,
"tvdb": { "tvdb": {
"id": 78901, "id": 78901,
"season": 10, "season": 10,
"episode": 17 "episode": 17
}
}, },
{ "imdb": {
"id": "257140", "id": 78901
"hash": "BE3BA5396B9A30544353B55FDD89EDE46C8FB72A",
"leechers": 0,
"seeders": 18,
"name": "Scandal S04E18 1080p WEB-DL DD5.1 H.264-ECI",
"times_completed": 19,
"size": 1789106197,
"utadded": 1428179128,
"added": "2015-04-04T20:25:28+0000",
"comments": 0,
"numfiles": 1,
"filename": "Scandal.2012.S04E18.1080p.WEB-DL.DD5.1.H.264-ECI.torrent",
"freeleech": "no",
"type_category": 2,
"type_codec": 1,
"type_medium": 6,
"type_origin": 0,
"username": "abc",
"owner": 1107944,
"tvdb": {
"id": 248841,
"season": 4,
"episode": 18
}
} }
},
{
"id": "257140",
"hash": "BE3BA5396B9A30544353B55FDD89EDE46C8FB72A",
"leechers": 0,
"seeders": 18,
"name": "Scandal S04E18 1080p WEB-DL DD5.1 H.264-ECI",
"times_completed": 19,
"size": 1789106197,
"utadded": 1428179128,
"added": "2015-04-04T20:25:28+0000",
"comments": 0,
"numfiles": 1,
"filename": "Scandal.2012.S04E18.1080p.WEB-DL.DD5.1.H.264-ECI.torrent",
"freeleech": "no",
"type_category": 2,
"type_codec": 1,
"type_medium": 6,
"type_origin": 0,
"username": "abc",
"owner": 1107944,
"tvdb": {
"id": 248841,
"season": 4,
"episode": 18
},
"imdb": {
"id": 78901
}
}
] ]
} }

File diff suppressed because one or more lines are too long

View File

@@ -11,12 +11,12 @@ using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
{ {
[TestFixture] [TestFixture]
public class CleanupOrphanedEpisodeFilesFixture : DbTest<CleanupOrphanedEpisodeFiles, EpisodeFile> public class CleanupOrphanedEpisodeFilesFixture : DbTest<CleanupOrphanedEpisodeFiles, MovieFile>
{ {
[Test] [Test]
public void should_delete_orphaned_episode_files() public void should_delete_orphaned_episode_files()
{ {
var episodeFile = Builder<EpisodeFile>.CreateNew() var episodeFile = Builder<MovieFile>.CreateNew()
.With(h => h.Quality = new QualityModel()) .With(h => h.Quality = new QualityModel())
.BuildNew(); .BuildNew();
@@ -28,22 +28,22 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
[Test] [Test]
public void should_not_delete_unorphaned_episode_files() public void should_not_delete_unorphaned_episode_files()
{ {
var episodeFiles = Builder<EpisodeFile>.CreateListOfSize(2) var episodeFiles = Builder<MovieFile>.CreateListOfSize(2)
.All() .All()
.With(h => h.Quality = new QualityModel()) .With(h => h.Quality = new QualityModel())
.BuildListOfNew(); .BuildListOfNew();
Db.InsertMany(episodeFiles); Db.InsertMany(episodeFiles);
var episode = Builder<Episode>.CreateNew() var episode = Builder<Movie>.CreateNew()
.With(e => e.EpisodeFileId = episodeFiles.First().Id) .With(e => e.MovieFileId = episodeFiles.First().Id)
.BuildNew(); .BuildNew();
Db.Insert(episode); Db.Insert(episode);
Subject.Clean(); Subject.Clean();
AllStoredModels.Should().HaveCount(1); AllStoredModels.Should().HaveCount(1);
Db.All<Episode>().Should().Contain(e => e.EpisodeFileId == AllStoredModels.First().Id); Db.All<Movie>().Should().Contain(e => e.MovieFileId == AllStoredModels.First().Id);
} }
} }
} }

View File

@@ -1,9 +1,12 @@
using FluentAssertions; using System;
using System.Xml;
using FluentAssertions;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Newznab; using NzbDrone.Core.Indexers.Newznab;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.IndexerTests.NewznabTests namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
{ {
@@ -64,5 +67,35 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
caps.DefaultPageSize.Should().Be(100); caps.DefaultPageSize.Should().Be(100);
caps.MaxPageSize.Should().Be(100); caps.MaxPageSize.Should().Be(100);
} }
[Test]
public void should_throw_if_failed_to_get()
{
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Get(It.IsAny<HttpRequest>()))
.Throws<Exception>();
Assert.Throws<Exception>(() => Subject.GetCapabilities(_settings));
}
[Test]
public void should_throw_if_xml_invalid()
{
GivenCapsResponse(_caps.Replace("<limits", "<>"));
Assert.Throws<XmlException>(() => Subject.GetCapabilities(_settings));
}
[Test]
public void should_not_throw_on_xml_data_unexpected()
{
GivenCapsResponse(_caps.Replace("5030", "asdf"));
var result = Subject.GetCapabilities(_settings);
result.Should().NotBeNull();
ExceptionVerification.ExpectedErrors(1);
}
} }
} }

View File

@@ -0,0 +1,69 @@
using System;
using System.Linq;
using System.Text;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.PassThePopcorn;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.IndexerTests.PTPTests
{
[TestFixture]
public class PTPFixture : CoreTest<PassThePopcorn>
{
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
{
Name = "PTP",
Settings = new PassThePopcornSettings() { Passkey = "fakekey", Username = "asdf", Password = "sad" }
};
}
[TestCase("Files/Indexers/PTP/imdbsearch.json")]
public void should_parse_feed_from_PTP(string fileName)
{
var authResponse = new PassThePopcornAuthResponse { Result = "Ok" };
System.IO.StringWriter authStream = new System.IO.StringWriter();
Json.Serialize(authResponse, authStream);
var responseJson = ReadAllText(fileName);
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.POST)))
.Returns<HttpRequest>(r => new HttpResponse(r,new HttpHeader(), authStream.ToString()));
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader {ContentType = HttpAccept.Json.Value}, responseJson));
var torrents = Subject.FetchRecent();
torrents.Should().HaveCount(293);
torrents.First().Should().BeOfType<PassThePopcornInfo>();
var first = torrents.First() as TorrentInfo;
first.Guid.Should().Be("PassThePopcorn-483521");
first.Title.Should().Be("The.Night.Of.S01.720p.HDTV.x264-BTN");
first.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
first.DownloadUrl.Should().Be("https://passthepopcorn.me/torrents.php?action=download&id=483521&authkey=00000000000000000000000000000000&torrent_pass=00000000000000000000000000000000");
first.InfoUrl.Should().Be("https://passthepopcorn.me/torrents.php?id=148131&torrentid=483521");
//first.PublishDate.Should().Be(DateTime.Parse("2017-04-17T12:13:42+0000").ToUniversalTime()); stupid timezones
first.Size.Should().Be(9370933376);
first.InfoHash.Should().BeNullOrEmpty();
first.MagnetUrl.Should().BeNullOrEmpty();
first.Peers.Should().Be(3);
first.Seeders.Should().Be(1);
torrents.Any(t => t.IndexerFlags.HasFlag(IndexerFlags.G_Freeleech)).Should().Be(true);
}
}
}

View File

@@ -297,6 +297,7 @@
<Compile Include="OrganizerTests\FileNameBuilderTests\EpisodeTitleCollapseFixture.cs" /> <Compile Include="OrganizerTests\FileNameBuilderTests\EpisodeTitleCollapseFixture.cs" />
<Compile Include="OrganizerTests\FileNameBuilderTests\MultiEpisodeFixture.cs" /> <Compile Include="OrganizerTests\FileNameBuilderTests\MultiEpisodeFixture.cs" />
<Compile Include="ParserTests\MiniSeriesEpisodeParserFixture.cs" /> <Compile Include="ParserTests\MiniSeriesEpisodeParserFixture.cs" />
<Compile Include="ParserTests\RomanNumeralTests\RomanNumeralConversionFixture.cs" />
<Compile Include="Qualities\RevisionComparableFixture.cs" /> <Compile Include="Qualities\RevisionComparableFixture.cs" />
<Compile Include="QueueTests\QueueServiceFixture.cs" /> <Compile Include="QueueTests\QueueServiceFixture.cs" />
<Compile Include="RemotePathMappingsTests\RemotePathMappingServiceFixture.cs" /> <Compile Include="RemotePathMappingsTests\RemotePathMappingServiceFixture.cs" />
@@ -385,6 +386,13 @@
<Compile Include="UpdateTests\UpdateServiceFixture.cs" /> <Compile Include="UpdateTests\UpdateServiceFixture.cs" />
<Compile Include="XbmcVersionTests.cs" /> <Compile Include="XbmcVersionTests.cs" />
<Compile Include="BulkImport\AddMultiMoviesFixture.cs" /> <Compile Include="BulkImport\AddMultiMoviesFixture.cs" />
<Content Include="Files\ArabicRomanNumeralDictionary.JSON">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Compile Include="IndexerTests\PTPTests\PTPFixture.cs" />
<None Include="Files\Indexers\PTP\imdbsearch.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Marr.Data\Marr.Data.csproj"> <ProjectReference Include="..\Marr.Data\Marr.Data.csproj">
@@ -570,6 +578,8 @@
<Folder Include="DataAugmentation\SceneNumbering\" /> <Folder Include="DataAugmentation\SceneNumbering\" />
<Folder Include="Providers\" /> <Folder Include="Providers\" />
<Folder Include="ProviderTests\UpdateProviderTests\" /> <Folder Include="ProviderTests\UpdateProviderTests\" />
<Folder Include="IndexerTests\PTPTests\" />
<Folder Include="Files\Indexers\PTP\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" /> <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />

View File

@@ -1,4 +1,4 @@
using FluentAssertions; using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
@@ -74,6 +74,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("To.Live.and.Die.in.L.A.1985.1080p.BluRay", "To Live and Die in L.A.")] [TestCase("To.Live.and.Die.in.L.A.1985.1080p.BluRay", "To Live and Die in L.A.")]
[TestCase("A.I.Artificial.Intelligence.(2001)", "A.I. Artificial Intelligence")] [TestCase("A.I.Artificial.Intelligence.(2001)", "A.I. Artificial Intelligence")]
[TestCase("A.Movie.Name.(1998)", "A Movie Name")] [TestCase("A.Movie.Name.(1998)", "A Movie Name")]
[TestCase("Thor: The Dark World 2013", "Thor The Dark World")]
public void should_parse_movie_title(string postTitle, string title) public void should_parse_movie_title(string postTitle, string title)
{ {
Parser.Parser.ParseMovieTitle(postTitle).MovieTitle.Should().Be(title); Parser.Parser.ParseMovieTitle(postTitle).MovieTitle.Should().Be(title);

View File

@@ -23,6 +23,8 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
private ParsedMovieInfo _wrongYearInfo; private ParsedMovieInfo _wrongYearInfo;
private ParsedMovieInfo _romanTitleInfo; private ParsedMovieInfo _romanTitleInfo;
private ParsedMovieInfo _alternativeTitleInfo; private ParsedMovieInfo _alternativeTitleInfo;
private ParsedMovieInfo _umlautInfo;
private ParsedMovieInfo _umlautAltInfo;
private MovieSearchCriteria _movieSearchCriteria; private MovieSearchCriteria _movieSearchCriteria;
private List<Episode> _episodes; private List<Episode> _episodes;
private ParsedEpisodeInfo _parsedEpisodeInfo; private ParsedEpisodeInfo _parsedEpisodeInfo;
@@ -37,10 +39,10 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
.Build(); .Build();
_movie = Builder<Movie>.CreateNew() _movie = Builder<Movie>.CreateNew()
.With(m => m.Title = "Mission Impossible 3") .With(m => m.Title = "Fack Ju Göthe 2")
.With(m => m.CleanTitle = "missionimpossible3") .With(m => m.CleanTitle = "fackjugoethe2")
.With(m => m.Year = 2006) .With(m => m.Year = 2015)
.With(m => m.AlternativeTitles = new List<string> { "Mission Impossible 3: Same same" }) .With(m => m.AlternativeTitles = new List<string> { "Fack Ju Göthe 2: Same same" })
.Build(); .Build();
_episodes = Builder<Episode>.CreateListOfSize(1) _episodes = Builder<Episode>.CreateListOfSize(1)
@@ -77,10 +79,22 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
_romanTitleInfo = new ParsedMovieInfo _romanTitleInfo = new ParsedMovieInfo
{ {
MovieTitle = "Mission Impossible III", MovieTitle = "Fack Ju Göthe II",
Year = _movie.Year, Year = _movie.Year,
}; };
_umlautInfo = new ParsedMovieInfo
{
MovieTitle = "Fack Ju Goethe 2",
Year = _movie.Year
};
_umlautAltInfo = new ParsedMovieInfo
{
MovieTitle = "Fack Ju Goethe 2: Same same",
Year = _movie.Year
};
_singleEpisodeSearchCriteria = new SingleEpisodeSearchCriteria _singleEpisodeSearchCriteria = new SingleEpisodeSearchCriteria
{ {
Series = _series, Series = _series,
@@ -148,5 +162,12 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
Subject.Map(_romanTitleInfo, "", _movieSearchCriteria).Movie.Should().Be(_movieSearchCriteria.Movie); Subject.Map(_romanTitleInfo, "", _movieSearchCriteria).Movie.Should().Be(_movieSearchCriteria.Movie);
} }
[Test]
public void should_match_umlauts()
{
Subject.Map(_umlautInfo, "", _movieSearchCriteria).Movie.Should().Be(_movieSearchCriteria.Movie);
Subject.Map(_umlautAltInfo, "", _movieSearchCriteria).Movie.Should().Be(_movieSearchCriteria.Movie);
}
} }
} }

View File

@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using NUnit.Framework;
using NzbDrone.Core.Parser.RomanNumerals;
namespace NzbDrone.Core.Test.ParserTests.RomanNumeralTests
{
[TestFixture]
public class RomanNumeralConversionFixture
{
private const string TEST_VALUES = @"Files/ArabicRomanNumeralDictionary.JSON";
private Dictionary<int, string> _arabicToRomanNumeralsMapping;
[OneTimeSetUp]
public void PopulateDictionaryWithProvenValues()
{
var pathToTestValues = Path.Combine(TestContext.CurrentContext.TestDirectory, Path.Combine(TEST_VALUES.Split('/')));
_arabicToRomanNumeralsMapping =
JsonConvert.DeserializeObject<Dictionary<int, string>>(File.ReadAllText(pathToTestValues));
}
[Test(Description = "Converts the supported range [1-3999] of Arabic to Roman numerals.")]
[Order(0)]
public void should_convert_arabic_numeral_to_roman_numeral([Range(1,3999)] int arabicNumeral)
{
RomanNumeral romanNumeral = new RomanNumeral(arabicNumeral);
string expectedValue = _arabicToRomanNumeralsMapping[arabicNumeral];
Assert.AreEqual(romanNumeral.ToRomanNumeral(), expectedValue);
}
[Test]
[Order(1)]
public void should_convert_roman_numeral_to_arabic_numeral([Range(1, 3999)] int arabicNumeral)
{
RomanNumeral romanNumeral = new RomanNumeral(_arabicToRomanNumeralsMapping[arabicNumeral]);
int expectecdValue = arabicNumeral;
Assert.AreEqual(romanNumeral.ToInt(), expectecdValue);
}
}
}

View File

@@ -105,11 +105,11 @@ namespace NzbDrone.Core.Configuration
set { SetValue("RssSyncInterval", value); } set { SetValue("RssSyncInterval", value); }
} }
public int AvailabilityDelay public int AvailabilityDelay
{ {
get { return GetValueInt("AvailabilityDelay",0); } get { return GetValueInt("AvailabilityDelay",0); }
set { SetValue("AvailabilityDelay", value); } set { SetValue("AvailabilityDelay", value); }
} }
public int NetImportSyncInterval public int NetImportSyncInterval
{ {
@@ -190,6 +190,27 @@ namespace NzbDrone.Core.Configuration
set { SetValue("EnableCompletedDownloadHandling", value); } set { SetValue("EnableCompletedDownloadHandling", value); }
} }
public bool PreferIndexerFlags
{
get { return GetValueBoolean("PreferIndexerFlags", false); }
set {SetValue("PreferIndexerFlags", value);}
}
public bool AllowHardcodedSubs
{
get { return GetValueBoolean("AllowHardcodedSubs", false); }
set { SetValue("AllowHardcodedSubs", value); }
}
public string WhitelistedHardcodedSubs
{
get { return GetValue("WhitelistedHardcodedSubs", ""); }
set { SetValue("WhitelistedHardcodedSubs", value); }
}
public bool RemoveCompletedDownloads public bool RemoveCompletedDownloads
{ {
get { return GetValueBoolean("RemoveCompletedDownloads", false); } get { return GetValueBoolean("RemoveCompletedDownloads", false); }

View File

@@ -46,17 +46,22 @@ namespace NzbDrone.Core.Configuration
int RssSyncInterval { get; set; } int RssSyncInterval { get; set; }
int MinimumAge { get; set; } int MinimumAge { get; set; }
int AvailabilityDelay { get; set; } bool PreferIndexerFlags { get; set; }
int AvailabilityDelay { get; set; }
bool AllowHardcodedSubs { get; set; }
string WhitelistedHardcodedSubs { get; set; }
int NetImportSyncInterval { get; set; } int NetImportSyncInterval { get; set; }
string ListSyncLevel { get; set; } string ListSyncLevel { get; set; }
string ImportExclusions { get; set; } string ImportExclusions { get; set; }
string TraktAuthToken { get; set; } string TraktAuthToken { get; set; }
string TraktRefreshToken { get; set; } string TraktRefreshToken { get; set; }
int TraktTokenExpiry { get; set; } int TraktTokenExpiry { get; set; }
string NewTraktAuthToken { get; set; } string NewTraktAuthToken { get; set; }
string NewTraktRefreshToken {get; set; } string NewTraktRefreshToken {get; set; }
int NewTraktTokenExpiry { get; set; } int NewTraktTokenExpiry { get; set; }
//UI //UI
int FirstDayOfWeek { get; set; } int FirstDayOfWeek { get; set; }

View File

@@ -159,6 +159,7 @@ namespace NzbDrone.Core.Datastore
MapRepository.Instance.RegisterTypeConverter(typeof(List<int>), new EmbeddedDocumentConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(List<int>), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(List<string>), new EmbeddedDocumentConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(List<string>), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(ParsedEpisodeInfo), new EmbeddedDocumentConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(ParsedEpisodeInfo), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(ParsedMovieInfo), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(ReleaseInfo), new EmbeddedDocumentConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(ReleaseInfo), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(HashSet<int>), new EmbeddedDocumentConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(HashSet<int>), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(OsPath), new OsPathConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(OsPath), new OsPathConverter());

View File

@@ -4,18 +4,21 @@ using System.Linq;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Delay; using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.DecisionEngine namespace NzbDrone.Core.DecisionEngine
{ {
public class DownloadDecisionComparer : IComparer<DownloadDecision> public class DownloadDecisionComparer : IComparer<DownloadDecision>
{ {
private readonly IDelayProfileService _delayProfileService; private readonly IDelayProfileService _delayProfileService;
private readonly IConfigService _configService;
public delegate int CompareDelegate(DownloadDecision x, DownloadDecision y); public delegate int CompareDelegate(DownloadDecision x, DownloadDecision y);
public delegate int CompareDelegate<TSubject, TValue>(DownloadDecision x, DownloadDecision y); public delegate int CompareDelegate<TSubject, TValue>(DownloadDecision x, DownloadDecision y);
public DownloadDecisionComparer(IDelayProfileService delayProfileService) public DownloadDecisionComparer(IDelayProfileService delayProfileService, IConfigService configService)
{ {
_delayProfileService = delayProfileService; _delayProfileService = delayProfileService;
_configService = configService;
} }
public int Compare(DownloadDecision x, DownloadDecision y) public int Compare(DownloadDecision x, DownloadDecision y)
@@ -24,6 +27,7 @@ namespace NzbDrone.Core.DecisionEngine
{ {
CompareQuality, CompareQuality,
ComparePreferredWords, ComparePreferredWords,
CompareIndexerFlags,
CompareProtocol, CompareProtocol,
ComparePeersIfTorrent, ComparePeersIfTorrent,
CompareAgeIfUsenet, CompareAgeIfUsenet,
@@ -84,7 +88,22 @@ namespace NzbDrone.Core.DecisionEngine
return num; return num;
}); });
; } }
private int CompareIndexerFlags(DownloadDecision x, DownloadDecision y)
{
var releaseX = x.RemoteMovie.Release;
var releaseY = y.RemoteMovie.Release;
if (_configService.PreferIndexerFlags)
{
return CompareBy(x.RemoteMovie.Release, y.RemoteMovie.Release, release => ScoreFlags(release.IndexerFlags));
}
else
{
return 0;
}
}
private int CompareProtocol(DownloadDecision x, DownloadDecision y) private int CompareProtocol(DownloadDecision x, DownloadDecision y)
{ {
@@ -185,5 +204,34 @@ namespace NzbDrone.Core.DecisionEngine
return CompareBy(x.RemoteMovie, y.RemoteMovie, remoteEpisode => remoteEpisode.Release.Size.Round(200.Megabytes())); return CompareBy(x.RemoteMovie, y.RemoteMovie, remoteEpisode => remoteEpisode.Release.Size.Round(200.Megabytes()));
} }
private int ScoreFlags(IndexerFlags flags)
{
var flagValues = Enum.GetValues(typeof(IndexerFlags));
var score = 0;
foreach (IndexerFlags value in flagValues)
{
if ((flags & value) == value)
{
switch (value)
{
case IndexerFlags.G_DoubleUpload:
case IndexerFlags.G_Freeleech:
case IndexerFlags.PTP_Approved:
case IndexerFlags.PTP_Golden:
case IndexerFlags.HDB_Internal:
score += 2;
break;
case IndexerFlags.G_Halfleech:
score += 1;
break;
}
}
}
return score;
}
} }
} }

View File

@@ -1,10 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Common.Serializer; using NzbDrone.Common.Serializer;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@@ -21,12 +22,14 @@ namespace NzbDrone.Core.DecisionEngine
{ {
private readonly IEnumerable<IDecisionEngineSpecification> _specifications; private readonly IEnumerable<IDecisionEngineSpecification> _specifications;
private readonly IParsingService _parsingService; private readonly IParsingService _parsingService;
private readonly IConfigService _configService;
private readonly Logger _logger; private readonly Logger _logger;
public DownloadDecisionMaker(IEnumerable<IDecisionEngineSpecification> specifications, IParsingService parsingService, Logger logger) public DownloadDecisionMaker(IEnumerable<IDecisionEngineSpecification> specifications, IParsingService parsingService, IConfigService configService, Logger logger)
{ {
_specifications = specifications; _specifications = specifications;
_parsingService = parsingService; _parsingService = parsingService;
_configService = configService;
_logger = logger; _logger = logger;
} }
@@ -68,30 +71,50 @@ namespace NzbDrone.Core.DecisionEngine
{ {
var parsedMovieInfo = Parser.Parser.ParseMovieTitle(report.Title); var parsedMovieInfo = Parser.Parser.ParseMovieTitle(report.Title);
if (parsedMovieInfo != null && !parsedMovieInfo.MovieTitle.IsNullOrWhiteSpace()) if (parsedMovieInfo != null && !parsedMovieInfo.MovieTitle.IsNullOrWhiteSpace())
{ {
RemoteMovie remoteMovie = _parsingService.Map(parsedMovieInfo, report.ImdbId.ToString(), searchCriteria); RemoteMovie remoteMovie = _parsingService.Map(parsedMovieInfo, report.ImdbId.ToString(), searchCriteria);
remoteMovie.Release = report; remoteMovie.Release = report;
if (remoteMovie.Movie == null) if (remoteMovie.Movie == null)
{ {
decision = new DownloadDecision(remoteMovie, new Rejection("Unknown movie. Cannot parse release name.")); decision = new DownloadDecision(remoteMovie, new Rejection("Unknown movie. Movie found does not match wanted movie."));
} }
else else
{ {
if (parsedMovieInfo.Quality.HardcodedSubs.IsNotNullOrWhiteSpace()) if (parsedMovieInfo.Quality.HardcodedSubs.IsNotNullOrWhiteSpace())
{ {
remoteMovie.DownloadAllowed = true; remoteMovie.DownloadAllowed = true;
decision = new DownloadDecision(remoteMovie, new Rejection("Hardcoded subs found: " + parsedMovieInfo.Quality.HardcodedSubs)); if (_configService.AllowHardcodedSubs)
} {
else decision = GetDecisionForReport(remoteMovie, searchCriteria);
{ }
remoteMovie.DownloadAllowed = true; else
decision = GetDecisionForReport(remoteMovie, searchCriteria); {
} var whitelisted = _configService.WhitelistedHardcodedSubs.Split(',');
_logger.Debug("Testing: {0}", whitelisted);
} if (whitelisted != null && whitelisted.Any(t => (parsedMovieInfo.Quality.HardcodedSubs.ToLower().Contains(t.ToLower()) && t.IsNotNullOrWhiteSpace())))
} {
decision = GetDecisionForReport(remoteMovie, searchCriteria);
}
else
{
decision = new DownloadDecision(remoteMovie, new Rejection("Hardcoded subs found: " + parsedMovieInfo.Quality.HardcodedSubs));
}
}
}
else
{
remoteMovie.DownloadAllowed = true;
decision = GetDecisionForReport(remoteMovie, searchCriteria);
}
}
}
else
{
_logger.Trace("{0} could not be parsed :(.", report.Title);
}
} }
catch (Exception e) catch (Exception e)
{ {

View File

@@ -1,6 +1,7 @@
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Core.Profiles.Delay; using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.DecisionEngine namespace NzbDrone.Core.DecisionEngine
{ {
@@ -13,10 +14,12 @@ namespace NzbDrone.Core.DecisionEngine
public class DownloadDecisionPriorizationService : IPrioritizeDownloadDecision public class DownloadDecisionPriorizationService : IPrioritizeDownloadDecision
{ {
private readonly IDelayProfileService _delayProfileService; private readonly IDelayProfileService _delayProfileService;
private readonly IConfigService _configService;
public DownloadDecisionPriorizationService(IDelayProfileService delayProfileService) public DownloadDecisionPriorizationService(IDelayProfileService delayProfileService, IConfigService configService)
{ {
_delayProfileService = delayProfileService; _delayProfileService = delayProfileService;
_configService = configService;
} }
public List<DownloadDecision> PrioritizeDecisions(List<DownloadDecision> decisions) public List<DownloadDecision> PrioritizeDecisions(List<DownloadDecision> decisions)
@@ -24,7 +27,7 @@ namespace NzbDrone.Core.DecisionEngine
return decisions.Where(c => c.RemoteEpisode.Series != null) return decisions.Where(c => c.RemoteEpisode.Series != null)
.GroupBy(c => c.RemoteEpisode.Series.Id, (seriesId, downloadDecisions) => .GroupBy(c => c.RemoteEpisode.Series.Id, (seriesId, downloadDecisions) =>
{ {
return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_delayProfileService)); return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_delayProfileService, _configService));
}) })
.SelectMany(c => c) .SelectMany(c => c)
.Union(decisions.Where(c => c.RemoteEpisode.Series == null)) .Union(decisions.Where(c => c.RemoteEpisode.Series == null))
@@ -36,7 +39,7 @@ namespace NzbDrone.Core.DecisionEngine
return decisions.Where(c => c.RemoteMovie.Movie != null) return decisions.Where(c => c.RemoteMovie.Movie != null)
.GroupBy(c => c.RemoteMovie.Movie.Id, (movieId, downloadDecisions) => .GroupBy(c => c.RemoteMovie.Movie.Id, (movieId, downloadDecisions) =>
{ {
return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_delayProfileService)); return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_delayProfileService, _configService));
}) })
.SelectMany(c => c) .SelectMany(c => c)
.Union(decisions.Where(c => c.RemoteMovie.Movie == null)) .Union(decisions.Where(c => c.RemoteMovie.Movie == null))

View File

@@ -1,4 +1,4 @@
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
@@ -121,6 +121,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
} }
var qualityDefinition = _qualityDefinitionService.Get(quality); var qualityDefinition = _qualityDefinitionService.Get(quality);
if (subject.Movie.Runtime == 0)
{
_logger.Info("{0} has no runtime information using median movie runtime of 110 minutes.", subject.Movie);
subject.Movie.Runtime = 110;
}
if (qualityDefinition.MinSize.HasValue) if (qualityDefinition.MinSize.HasValue)
{ {
var minSize = qualityDefinition.MinSize.Value.Megabytes(); var minSize = qualityDefinition.MinSize.Value.Megabytes();

View File

@@ -1,4 +1,4 @@
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Core.Download.Pending; using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;

View File

@@ -1,13 +1,19 @@
namespace NzbDrone.Core.Download.Clients.DownloadStation namespace NzbDrone.Core.Download.Clients.DownloadStation
{ {
public class DiskStationApiInfo public class DiskStationApiInfo
{ {
private string _path; private string _path;
public int MaxVersion { get; set; } public int MaxVersion { get; set; }
public int MinVersion { get; set; } public int MinVersion { get; set; }
public DiskStationApi Type { get; set; }
public string Name { get; set; }
public bool NeedsAuthentication { get; set; }
public string Path public string Path
{ {
get { return _path; } get { return _path; }

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NLog; using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Download.Clients.DownloadStation.Responses; using NzbDrone.Core.Download.Clients.DownloadStation.Responses;
@@ -13,20 +14,19 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
public class DSMInfoProxy : DiskStationProxyBase, IDSMInfoProxy public class DSMInfoProxy : DiskStationProxyBase, IDSMInfoProxy
{ {
public DSMInfoProxy(IHttpClient httpClient, Logger logger) : public DSMInfoProxy(IHttpClient httpClient, ICacheManager cacheManager, Logger logger) :
base(httpClient, logger) base(DiskStationApi.DSMInfo, "SYNO.DSM.Info", httpClient, cacheManager, logger)
{ {
} }
public string GetSerialNumber(DownloadStationSettings settings) public string GetSerialNumber(DownloadStationSettings settings)
{ {
var arguments = new Dictionary<string, object>() { var info = GetApiInfo(settings);
{ "api", "SYNO.DSM.Info" },
{ "version", "2" }, var requestBuilder = BuildRequest(settings, "getinfo", info.MinVersion);
{ "method", "getinfo" }
}; var response = ProcessRequest<DSMInfoResponse>(requestBuilder, "get serial number", settings);
var response = ProcessRequest<DSMInfoResponse>(DiskStationApi.DSMInfo, arguments, settings, "get serial number");
return response.Data.SerialNumber; return response.Data.SerialNumber;
} }
} }

View File

@@ -1,63 +1,77 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using NLog; using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer; using NzbDrone.Common.Serializer;
using NzbDrone.Core.Download.Clients.DownloadStation.Responses; using NzbDrone.Core.Download.Clients.DownloadStation.Responses;
namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
{ {
public abstract class DiskStationProxyBase public interface IDiskStationProxy
{ {
private static readonly Dictionary<DiskStationApi, string> Resources; DiskStationApiInfo GetApiInfo(DownloadStationSettings settings);
}
public abstract class DiskStationProxyBase : IDiskStationProxy
{
protected readonly Logger _logger;
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
protected readonly Logger _logger; private readonly ICached<DiskStationApiInfo> _infoCache;
private bool _authenticated; private readonly ICached<string> _sessionCache;
private readonly DiskStationApi _apiType;
private readonly string _apiName;
private static readonly DiskStationApiInfo _apiInfo;
static DiskStationProxyBase() static DiskStationProxyBase()
{ {
Resources = new Dictionary<DiskStationApi, string> _apiInfo = new DiskStationApiInfo()
{ {
{ DiskStationApi.Info, "query.cgi" } Type = DiskStationApi.Info,
Name = "SYNO.API.Info",
Path = "query.cgi",
MaxVersion = 1,
MinVersion = 1,
NeedsAuthentication = false
}; };
} }
public DiskStationProxyBase(IHttpClient httpClient, Logger logger) public DiskStationProxyBase(DiskStationApi apiType,
string apiName,
IHttpClient httpClient,
ICacheManager cacheManager,
Logger logger)
{ {
_httpClient = httpClient; _httpClient = httpClient;
_logger = logger; _logger = logger;
_infoCache = cacheManager.GetCache<DiskStationApiInfo>(typeof(DiskStationProxyBase), "apiInfo");
_sessionCache = cacheManager.GetCache<string>(typeof(DiskStationProxyBase), "sessions");
_apiType = apiType;
_apiName = apiName;
} }
private string GenerateSessionCacheKey(DownloadStationSettings settings)
protected DiskStationResponse<object> ProcessRequest(DiskStationApi api,
Dictionary<string, object> arguments,
DownloadStationSettings settings,
string operation,
HttpMethod method = HttpMethod.GET)
{ {
return ProcessRequest<object>(api, arguments, settings, operation, method); return $"{settings.Username}@{settings.Host}:{settings.Port}";
} }
protected DiskStationResponse<T> ProcessRequest<T>(DiskStationApi api, protected DiskStationResponse<T> ProcessRequest<T>(HttpRequestBuilder requestBuilder,
Dictionary<string, object> arguments, string operation,
DownloadStationSettings settings, DownloadStationSettings settings) where T : new()
string operation,
HttpMethod method = HttpMethod.GET,
int retries = 0) where T : new()
{ {
if (retries == 5) return ProcessRequest<T>(requestBuilder, operation, _apiType, settings);
{ }
throw new DownloadClientException("Try to process request to {0} with {1} more than 5 times", api, arguments.ToJson().ToString());
}
if (!_authenticated && api != DiskStationApi.Info && api != DiskStationApi.DSMInfo) private DiskStationResponse<T> ProcessRequest<T>(HttpRequestBuilder requestBuilder,
{ string operation,
AuthenticateClient(settings); DiskStationApi api,
} DownloadStationSettings settings) where T : new()
{
var request = BuildRequest(settings, api, arguments, method); var request = requestBuilder.Build();
var response = _httpClient.Execute(request); var response = _httpClient.Execute(request);
_logger.Debug("Trying to {0}", operation); _logger.Debug("Trying to {0}", operation);
@@ -77,16 +91,14 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
if (responseContent.Error.SessionError) if (responseContent.Error.SessionError)
{ {
_authenticated = false; _sessionCache.Remove(GenerateSessionCacheKey(settings));
if (responseContent.Error.Code == 105) if (responseContent.Error.Code == 105)
{ {
throw new DownloadClientAuthenticationException(msg); throw new DownloadClientAuthenticationException(msg);
} }
return ProcessRequest<T>(api, arguments, settings, operation, method, ++retries);
} }
throw new DownloadClientException(msg); throw new DownloadClientException(msg);
} }
} }
@@ -96,124 +108,126 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
} }
} }
private void AuthenticateClient(DownloadStationSettings settings) private string AuthenticateClient(DownloadStationSettings settings)
{ {
var arguments = new Dictionary<string, object> var authInfo = GetApiInfo(DiskStationApi.Auth, settings);
{
{ "api", "SYNO.API.Auth" },
{ "version", "1" },
{ "method", "login" },
{ "account", settings.Username },
{ "passwd", settings.Password },
{ "format", "cookie" },
{ "session", "DownloadStation" },
};
var authLoginRequest = BuildRequest(settings, DiskStationApi.Auth, arguments, HttpMethod.GET); var requestBuilder = BuildRequest(settings, authInfo, "login", 2);
authLoginRequest.StoreResponseCookie = true; requestBuilder.AddQueryParam("account", settings.Username);
requestBuilder.AddQueryParam("passwd", settings.Password);
requestBuilder.AddQueryParam("format", "sid");
requestBuilder.AddQueryParam("session", "DownloadStation");
var response = _httpClient.Execute(authLoginRequest); var authResponse = ProcessRequest<DiskStationAuthResponse>(requestBuilder, "login", DiskStationApi.Auth, settings);
var downloadStationResponse = Json.Deserialize<DiskStationResponse<DiskStationAuthResponse>>(response.Content); return authResponse.Data.SId;
var authResponse = Json.Deserialize<DiskStationResponse<DiskStationAuthResponse>>(response.Content);
_authenticated = authResponse.Success;
if (!_authenticated)
{
throw new DownloadClientAuthenticationException(downloadStationResponse.Error.GetMessage(DiskStationApi.Auth));
}
} }
private HttpRequest BuildRequest(DownloadStationSettings settings, DiskStationApi api, Dictionary<string, object> arguments, HttpMethod method) protected HttpRequestBuilder BuildRequest(DownloadStationSettings settings, string methodName, int apiVersion, HttpMethod httpVerb = HttpMethod.GET)
{ {
if (!Resources.ContainsKey(api)) var info = GetApiInfo(_apiType, settings);
{
GetApiVersion(settings, api);
}
var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port).Resource($"webapi/{Resources[api]}"); return BuildRequest(settings, info, methodName, apiVersion, httpVerb);
requestBuilder.Method = method; }
private HttpRequestBuilder BuildRequest(DownloadStationSettings settings, DiskStationApiInfo apiInfo, string methodName, int apiVersion, HttpMethod httpVerb = HttpMethod.GET)
{
var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port).Resource($"webapi/{apiInfo.Path}");
requestBuilder.Method = httpVerb;
requestBuilder.LogResponseContent = true; requestBuilder.LogResponseContent = true;
requestBuilder.SuppressHttpError = true; requestBuilder.SuppressHttpError = true;
requestBuilder.AllowAutoRedirect = false; requestBuilder.AllowAutoRedirect = false;
requestBuilder.Headers.ContentType = "application/json";
if (requestBuilder.Method == HttpMethod.POST) if (apiVersion < apiInfo.MinVersion || apiVersion > apiInfo.MaxVersion)
{ {
if (api == DiskStationApi.DownloadStationTask && arguments.ContainsKey("file")) throw new ArgumentOutOfRangeException(nameof(apiVersion));
{ }
requestBuilder.Headers.ContentType = "multipart/form-data";
foreach (var arg in arguments) if (httpVerb == HttpMethod.POST)
{ {
if (arg.Key == "file") if (apiInfo.NeedsAuthentication)
{
Dictionary<string, object> file = (Dictionary<string, object>)arg.Value;
requestBuilder.AddFormUpload(arg.Key, file["name"].ToString(), (byte[])file["data"]);
}
else
{
requestBuilder.AddFormParameter(arg.Key, arg.Value);
}
}
}
else
{ {
requestBuilder.Headers.ContentType = "application/json"; requestBuilder.AddFormParameter("_sid", _sessionCache.Get(GenerateSessionCacheKey(settings), () => AuthenticateClient(settings), TimeSpan.FromHours(6)));
} }
requestBuilder.AddFormParameter("api", apiInfo.Name);
requestBuilder.AddFormParameter("version", apiVersion);
requestBuilder.AddFormParameter("method", methodName);
} }
else else
{ {
foreach (var arg in arguments) if (apiInfo.NeedsAuthentication)
{ {
requestBuilder.AddQueryParam(arg.Key, arg.Value); requestBuilder.AddQueryParam("_sid", _sessionCache.Get(GenerateSessionCacheKey(settings), () => AuthenticateClient(settings), TimeSpan.FromHours(6)));
}
requestBuilder.AddQueryParam("api", apiInfo.Name);
requestBuilder.AddQueryParam("version", apiVersion);
requestBuilder.AddQueryParam("method", methodName);
}
return requestBuilder;
}
private string GenerateInfoCacheKey(DownloadStationSettings settings, DiskStationApi api)
{
return $"{settings.Host}:{settings.Port}->{api}";
}
private void UpdateApiInfo(DownloadStationSettings settings)
{
var apis = new Dictionary<string, DiskStationApi>()
{
{ "SYNO.API.Auth", DiskStationApi.Auth },
{ _apiName, _apiType }
};
var requestBuilder = BuildRequest(settings, _apiInfo, "query", _apiInfo.MinVersion);
requestBuilder.AddQueryParam("query", string.Join(",", apis.Keys));
var infoResponse = ProcessRequest<DiskStationApiInfoResponse>(requestBuilder, "get api info", _apiInfo.Type, settings);
foreach (var data in infoResponse.Data)
{
if (apis.ContainsKey(data.Key))
{
data.Value.Name = data.Key;
data.Value.Type = apis[data.Key];
data.Value.NeedsAuthentication = apis[data.Key] != DiskStationApi.Auth;
_infoCache.Set(GenerateInfoCacheKey(settings, apis[data.Key]), data.Value, TimeSpan.FromHours(1));
}
}
}
private DiskStationApiInfo GetApiInfo(DiskStationApi api, DownloadStationSettings settings)
{
if (api == DiskStationApi.Info)
{
return _apiInfo;
}
var key = GenerateInfoCacheKey(settings, api);
var info = _infoCache.Find(key);
if (info == null)
{
UpdateApiInfo(settings);
info = _infoCache.Find(key);
if (info == null)
{
throw new DownloadClientException("Info of {0} not found on {1}:{2}", api, settings.Host, settings.Port);
} }
} }
return requestBuilder.Build(); return info;
} }
protected IEnumerable<int> GetApiVersion(DownloadStationSettings settings, DiskStationApi api) public DiskStationApiInfo GetApiInfo(DownloadStationSettings settings)
{ {
var arguments = new Dictionary<string, object> return GetApiInfo(_apiType, settings);
{
{ "api", "SYNO.API.Info" },
{ "version", "1" },
{ "method", "query" },
{ "query", "SYNO.API.Auth, SYNO.DownloadStation.Info, SYNO.DownloadStation.Task, SYNO.FileStation.List, SYNO.DSM.Info" },
};
var infoResponse = ProcessRequest<DiskStationApiInfoResponse>(DiskStationApi.Info, arguments, settings, "Get api version");
//TODO: Refactor this into more elegant code
var infoResponeDSAuth = infoResponse.Data["SYNO.API.Auth"];
var infoResponeDSInfo = infoResponse.Data["SYNO.DownloadStation.Info"];
var infoResponeDSTask = infoResponse.Data["SYNO.DownloadStation.Task"];
var infoResponseFSList = infoResponse.Data["SYNO.FileStation.List"];
var infoResponseDSMInfo = infoResponse.Data["SYNO.DSM.Info"];
Resources[DiskStationApi.Auth] = infoResponeDSAuth.Path;
Resources[DiskStationApi.DownloadStationInfo] = infoResponeDSInfo.Path;
Resources[DiskStationApi.DownloadStationTask] = infoResponeDSTask.Path;
Resources[DiskStationApi.FileStationList] = infoResponseFSList.Path;
Resources[DiskStationApi.DSMInfo] = infoResponseDSMInfo.Path;
switch (api)
{
case DiskStationApi.Auth:
return Enumerable.Range(infoResponeDSAuth.MinVersion, infoResponeDSAuth.MaxVersion - infoResponeDSAuth.MinVersion + 1);
case DiskStationApi.DownloadStationInfo:
return Enumerable.Range(infoResponeDSInfo.MinVersion, infoResponeDSInfo.MaxVersion - infoResponeDSInfo.MinVersion + 1);
case DiskStationApi.DownloadStationTask:
return Enumerable.Range(infoResponeDSTask.MinVersion, infoResponeDSTask.MaxVersion - infoResponeDSTask.MinVersion + 1);
case DiskStationApi.FileStationList:
return Enumerable.Range(infoResponseFSList.MinVersion, infoResponseFSList.MaxVersion - infoResponseFSList.MinVersion + 1);
case DiskStationApi.DSMInfo:
return Enumerable.Range(infoResponseDSMInfo.MinVersion, infoResponseDSMInfo.MaxVersion - infoResponseDSMInfo.MinVersion + 1);
default:
throw new DownloadClientException("Api not implemented");
}
} }
} }
} }

View File

@@ -0,0 +1,29 @@
using NLog;
using NzbDrone.Common.Http;
using System.Collections.Generic;
using NzbDrone.Common.Cache;
namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
{
public interface IDownloadStationInfoProxy : IDiskStationProxy
{
Dictionary<string, object> GetConfig(DownloadStationSettings settings);
}
public class DownloadStationInfoProxy : DiskStationProxyBase, IDownloadStationInfoProxy
{
public DownloadStationInfoProxy(IHttpClient httpClient, ICacheManager cacheManager, Logger logger) :
base(DiskStationApi.DownloadStationInfo, "SYNO.DownloadStation.Info", httpClient, cacheManager, logger)
{
}
public Dictionary<string, object> GetConfig(DownloadStationSettings settings)
{
var requestBuilder = BuildRequest(settings, "getConfig", 1);
var response = ProcessRequest<Dictionary<string, object>>(requestBuilder, "get config", settings);
return response.Data;
}
}
}

View File

@@ -1,121 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Download.Clients.DownloadStation.Responses;
namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
{
public interface IDownloadStationProxy
{
IEnumerable<DownloadStationTask> GetTasks(DownloadStationSettings settings);
Dictionary<string, object> GetConfig(DownloadStationSettings settings);
void RemoveTask(string downloadId, DownloadStationSettings settings);
void AddTaskFromUrl(string url, string downloadDirectory, DownloadStationSettings settings);
void AddTaskFromData(byte[] data, string filename, string downloadDirectory, DownloadStationSettings settings);
IEnumerable<int> GetApiVersion(DownloadStationSettings settings);
}
public class DownloadStationProxy : DiskStationProxyBase, IDownloadStationProxy
{
public DownloadStationProxy(IHttpClient httpClient, Logger logger)
: base(httpClient, logger)
{
}
public void AddTaskFromData(byte[] data, string filename, string downloadDirectory, DownloadStationSettings settings)
{
var arguments = new Dictionary<string, object>
{
{ "api", "SYNO.DownloadStation.Task" },
{ "version", "2" },
{ "method", "create" }
};
if (downloadDirectory.IsNotNullOrWhiteSpace())
{
arguments.Add("destination", downloadDirectory);
}
arguments.Add("file", new Dictionary<string, object>() { { "name", filename }, { "data", data } });
var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, $"add task from data {filename}", HttpMethod.POST);
}
public void AddTaskFromUrl(string url, string downloadDirectory, DownloadStationSettings settings)
{
var arguments = new Dictionary<string, object>
{
{ "api", "SYNO.DownloadStation.Task" },
{ "version", "3" },
{ "method", "create" },
{ "uri", url }
};
if (downloadDirectory.IsNotNullOrWhiteSpace())
{
arguments.Add("destination", downloadDirectory);
}
var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, $"add task from url {url}");
}
public IEnumerable<DownloadStationTask> GetTasks(DownloadStationSettings settings)
{
var arguments = new Dictionary<string, object>
{
{ "api", "SYNO.DownloadStation.Task" },
{ "version", "1" },
{ "method", "list" },
{ "additional", "detail,transfer" }
};
try
{
var response = ProcessRequest<DownloadStationTaskInfoResponse>(DiskStationApi.DownloadStationTask, arguments, settings, "get tasks");
return response.Data.Tasks;
}
catch (DownloadClientException e)
{
_logger.Error(e);
return new List<DownloadStationTask>();
}
}
public Dictionary<string, object> GetConfig(DownloadStationSettings settings)
{
var arguments = new Dictionary<string, object>
{
{ "api", "SYNO.DownloadStation.Info" },
{ "version", "1" },
{ "method", "getconfig" }
};
var response = ProcessRequest<Dictionary<string, object>>(DiskStationApi.DownloadStationInfo, arguments, settings, "get config");
return response.Data;
}
public void RemoveTask(string downloadId, DownloadStationSettings settings)
{
var arguments = new Dictionary<string, object>
{
{ "api", "SYNO.DownloadStation.Task" },
{ "version", "1" },
{ "method", "delete" },
{ "id", downloadId },
{ "force_complete", false }
};
var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, $"remove item {downloadId}");
}
public IEnumerable<int> GetApiVersion(DownloadStationSettings settings)
{
return base.GetApiVersion(settings, DiskStationApi.DownloadStationInfo);
}
}
}

View File

@@ -0,0 +1,79 @@
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Download.Clients.DownloadStation.Responses;
namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
{
public interface IDownloadStationTaskProxy : IDiskStationProxy
{
IEnumerable<DownloadStationTask> GetTasks(DownloadStationSettings settings);
void RemoveTask(string downloadId, DownloadStationSettings settings);
void AddTaskFromUrl(string url, string downloadDirectory, DownloadStationSettings settings);
void AddTaskFromData(byte[] data, string filename, string downloadDirectory, DownloadStationSettings settings);
}
public class DownloadStationTaskProxy : DiskStationProxyBase, IDownloadStationTaskProxy
{
public DownloadStationTaskProxy(IHttpClient httpClient, ICacheManager cacheManager, Logger logger)
: base(DiskStationApi.DownloadStationTask, "SYNO.DownloadStation.Task", httpClient, cacheManager, logger)
{
}
public void AddTaskFromData(byte[] data, string filename, string downloadDirectory, DownloadStationSettings settings)
{
var requestBuilder = BuildRequest(settings, "create", 2, HttpMethod.POST);
if (downloadDirectory.IsNotNullOrWhiteSpace())
{
requestBuilder.AddFormParameter("destination", downloadDirectory);
}
requestBuilder.AddFormUpload("file", filename, data);
var response = ProcessRequest<object>(requestBuilder, $"add task from data {filename}", settings);
}
public void AddTaskFromUrl(string url, string downloadDirectory, DownloadStationSettings settings)
{
var requestBuilder = BuildRequest(settings, "create", 3);
requestBuilder.AddQueryParam("uri", url);
if (downloadDirectory.IsNotNullOrWhiteSpace())
{
requestBuilder.AddQueryParam("destination", downloadDirectory);
}
var response = ProcessRequest<object>(requestBuilder, $"add task from url {url}", settings);
}
public IEnumerable<DownloadStationTask> GetTasks(DownloadStationSettings settings)
{
try
{
var requestBuilder = BuildRequest(settings, "list", 1);
requestBuilder.AddQueryParam("additional", "detail,transfer");
var response = ProcessRequest<DownloadStationTaskInfoResponse>(requestBuilder, "get tasks", settings);
return response.Data.Tasks;
}
catch (DownloadClientException e)
{
_logger.Error(e);
return new List<DownloadStationTask>();
}
}
public void RemoveTask(string downloadId, DownloadStationSettings settings)
{
var requestBuilder = BuildRequest(settings, "delete", 1);
requestBuilder.AddQueryParam("id", downloadId);
requestBuilder.AddQueryParam("force_complete", false);
var response = ProcessRequest<object>(requestBuilder, $"remove item {downloadId}", settings);
}
}
}

View File

@@ -2,31 +2,27 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer; using NzbDrone.Common.Serializer;
using NzbDrone.Core.Download.Clients.DownloadStation.Responses; using NzbDrone.Core.Download.Clients.DownloadStation.Responses;
namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
{ {
public interface IFileStationProxy public interface IFileStationProxy : IDiskStationProxy
{ {
SharedFolderMapping GetSharedFolderMapping(string sharedFolder, DownloadStationSettings settings); SharedFolderMapping GetSharedFolderMapping(string sharedFolder, DownloadStationSettings settings);
IEnumerable<int> GetApiVersion(DownloadStationSettings settings);
FileStationListFileInfoResponse GetInfoFileOrDirectory(string path, DownloadStationSettings settings); FileStationListFileInfoResponse GetInfoFileOrDirectory(string path, DownloadStationSettings settings);
} }
public class FileStationProxy : DiskStationProxyBase, IFileStationProxy public class FileStationProxy : DiskStationProxyBase, IFileStationProxy
{ {
public FileStationProxy(IHttpClient httpClient, Logger logger) public FileStationProxy(IHttpClient httpClient, ICacheManager cacheManager, Logger logger)
: base(httpClient, logger) : base(DiskStationApi.FileStationList, "SYNO.FileStation.List", httpClient, cacheManager, logger)
{ {
} }
public IEnumerable<int> GetApiVersion(DownloadStationSettings settings)
{
return base.GetApiVersion(settings, DiskStationApi.FileStationList);
}
public SharedFolderMapping GetSharedFolderMapping(string sharedFolder, DownloadStationSettings settings) public SharedFolderMapping GetSharedFolderMapping(string sharedFolder, DownloadStationSettings settings)
{ {
var info = GetInfoFileOrDirectory(sharedFolder, settings); var info = GetInfoFileOrDirectory(sharedFolder, settings);
@@ -38,16 +34,11 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
public FileStationListFileInfoResponse GetInfoFileOrDirectory(string path, DownloadStationSettings settings) public FileStationListFileInfoResponse GetInfoFileOrDirectory(string path, DownloadStationSettings settings)
{ {
var arguments = new Dictionary<string, object> var requestBuilder = BuildRequest(settings, "getinfo", 2);
{ requestBuilder.AddQueryParam("path", new[] { path }.ToJson());
{ "api", "SYNO.FileStation.List" }, requestBuilder.AddQueryParam("additional", "[\"real_path\"]");
{ "version", "2" },
{ "method", "getinfo" },
{ "path", new [] { path }.ToJson() },
{ "additional", $"[\"real_path\"]" }
};
var response = ProcessRequest<FileStationListResponse>(DiskStationApi.FileStationList, arguments, settings, $"get info of {path}"); var response = ProcessRequest<FileStationListResponse>(requestBuilder, $"get info of {path}", settings);
return response.Data.Files.First(); return response.Data.Files.First();
} }

View File

@@ -5,7 +5,6 @@ using System.Linq;
using System.Net; using System.Net;
using FluentValidation.Results; using FluentValidation.Results;
using NLog; using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
@@ -20,7 +19,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{ {
public class TorrentDownloadStation : TorrentClientBase<DownloadStationSettings> public class TorrentDownloadStation : TorrentClientBase<DownloadStationSettings>
{ {
protected readonly IDownloadStationProxy _proxy; protected readonly IDownloadStationInfoProxy _dsInfoProxy;
protected readonly IDownloadStationTaskProxy _dsTaskProxy;
protected readonly ISharedFolderResolver _sharedFolderResolver; protected readonly ISharedFolderResolver _sharedFolderResolver;
protected readonly ISerialNumberProvider _serialNumberProvider; protected readonly ISerialNumberProvider _serialNumberProvider;
protected readonly IFileStationProxy _fileStationProxy; protected readonly IFileStationProxy _fileStationProxy;
@@ -28,7 +28,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
public TorrentDownloadStation(ISharedFolderResolver sharedFolderResolver, public TorrentDownloadStation(ISharedFolderResolver sharedFolderResolver,
ISerialNumberProvider serialNumberProvider, ISerialNumberProvider serialNumberProvider,
IFileStationProxy fileStationProxy, IFileStationProxy fileStationProxy,
IDownloadStationProxy proxy, IDownloadStationInfoProxy dsInfoProxy,
IDownloadStationTaskProxy dsTaskProxy,
ITorrentFileInfoReader torrentFileInfoReader, ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient, IHttpClient httpClient,
IConfigService configService, IConfigService configService,
@@ -37,7 +38,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
Logger logger) Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger)
{ {
_proxy = proxy; _dsInfoProxy = dsInfoProxy;
_dsTaskProxy = dsTaskProxy;
_fileStationProxy = fileStationProxy; _fileStationProxy = fileStationProxy;
_sharedFolderResolver = sharedFolderResolver; _sharedFolderResolver = sharedFolderResolver;
_serialNumberProvider = serialNumberProvider; _serialNumberProvider = serialNumberProvider;
@@ -47,7 +49,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
protected IEnumerable<DownloadStationTask> GetTasks() protected IEnumerable<DownloadStationTask> GetTasks()
{ {
return _proxy.GetTasks(Settings).Where(v => v.Type.ToLower() == DownloadStationTaskType.BT.ToString().ToLower()); return _dsTaskProxy.GetTasks(Settings).Where(v => v.Type.ToLower() == DownloadStationTaskType.BT.ToString().ToLower());
} }
public override IEnumerable<DownloadClientItem> GetItems() public override IEnumerable<DownloadClientItem> GetItems()
@@ -129,7 +131,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
DeleteItemData(downloadId); DeleteItemData(downloadId);
} }
_proxy.RemoveTask(ParseDownloadId(downloadId), Settings); _dsTaskProxy.RemoveTask(ParseDownloadId(downloadId), Settings);
_logger.Debug("{0} removed correctly", downloadId); _logger.Debug("{0} removed correctly", downloadId);
} }
@@ -158,7 +160,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{ {
var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings); var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings);
_proxy.AddTaskFromUrl(magnetLink, GetDownloadDirectory(), Settings); _dsTaskProxy.AddTaskFromUrl(magnetLink, GetDownloadDirectory(), Settings);
var item = GetTasks().SingleOrDefault(t => t.Additional.Detail["uri"] == magnetLink); var item = GetTasks().SingleOrDefault(t => t.Additional.Detail["uri"] == magnetLink);
@@ -177,7 +179,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{ {
var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings); var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings);
_proxy.AddTaskFromData(fileContent, filename, GetDownloadDirectory(), Settings); _dsTaskProxy.AddTaskFromData(fileContent, filename, GetDownloadDirectory(), Settings);
var items = GetTasks().Where(t => t.Additional.Detail["uri"] == Path.GetFileNameWithoutExtension(filename)); var items = GetTasks().Where(t => t.Additional.Detail["uri"] == Path.GetFileNameWithoutExtension(filename));
@@ -368,13 +370,13 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
protected ValidationFailure ValidateVersion() protected ValidationFailure ValidateVersion()
{ {
var versionRange = _proxy.GetApiVersion(Settings); var info = _dsTaskProxy.GetApiInfo(Settings);
_logger.Debug("Download Station api version information: Min {0} - Max {1}", versionRange.Min(), versionRange.Max()); _logger.Debug("Download Station api version information: Min {0} - Max {1}", info.MinVersion, info.MaxVersion);
if (!versionRange.Contains(2)) if (info.MinVersion > 2 || info.MaxVersion < 2)
{ {
return new ValidationFailure(string.Empty, $"Download Station API version not supported, should be at least 2. It supports from {versionRange.Min()} to {versionRange.Max()}"); return new ValidationFailure(string.Empty, $"Download Station API version not supported, should be at least 2. It supports from {info.MinVersion} to {info.MaxVersion}");
} }
return null; return null;
@@ -405,7 +407,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
protected string GetDefaultDir() protected string GetDefaultDir()
{ {
var config = _proxy.GetConfig(Settings); var config = _dsInfoProxy.GetConfig(Settings);
var path = config["default_destination"] as string; var path = config["default_destination"] as string;

View File

@@ -17,7 +17,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{ {
public class UsenetDownloadStation : UsenetClientBase<DownloadStationSettings> public class UsenetDownloadStation : UsenetClientBase<DownloadStationSettings>
{ {
protected readonly IDownloadStationProxy _proxy; protected readonly IDownloadStationInfoProxy _dsInfoProxy;
protected readonly IDownloadStationTaskProxy _dsTaskProxy;
protected readonly ISharedFolderResolver _sharedFolderResolver; protected readonly ISharedFolderResolver _sharedFolderResolver;
protected readonly ISerialNumberProvider _serialNumberProvider; protected readonly ISerialNumberProvider _serialNumberProvider;
protected readonly IFileStationProxy _fileStationProxy; protected readonly IFileStationProxy _fileStationProxy;
@@ -25,7 +26,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
public UsenetDownloadStation(ISharedFolderResolver sharedFolderResolver, public UsenetDownloadStation(ISharedFolderResolver sharedFolderResolver,
ISerialNumberProvider serialNumberProvider, ISerialNumberProvider serialNumberProvider,
IFileStationProxy fileStationProxy, IFileStationProxy fileStationProxy,
IDownloadStationProxy proxy, IDownloadStationInfoProxy dsInfoProxy,
IDownloadStationTaskProxy dsTaskProxy,
IHttpClient httpClient, IHttpClient httpClient,
IConfigService configService, IConfigService configService,
IDiskProvider diskProvider, IDiskProvider diskProvider,
@@ -34,7 +36,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
) )
: base(httpClient, configService, diskProvider, remotePathMappingService, logger) : base(httpClient, configService, diskProvider, remotePathMappingService, logger)
{ {
_proxy = proxy; _dsInfoProxy = dsInfoProxy;
_dsTaskProxy = dsTaskProxy;
_fileStationProxy = fileStationProxy; _fileStationProxy = fileStationProxy;
_sharedFolderResolver = sharedFolderResolver; _sharedFolderResolver = sharedFolderResolver;
_serialNumberProvider = serialNumberProvider; _serialNumberProvider = serialNumberProvider;
@@ -44,7 +47,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
protected IEnumerable<DownloadStationTask> GetTasks() protected IEnumerable<DownloadStationTask> GetTasks()
{ {
return _proxy.GetTasks(Settings).Where(v => v.Type.ToLower() == DownloadStationTaskType.NZB.ToString().ToLower()); return _dsTaskProxy.GetTasks(Settings).Where(v => v.Type.ToLower() == DownloadStationTaskType.NZB.ToString().ToLower());
} }
public override IEnumerable<DownloadClientItem> GetItems() public override IEnumerable<DownloadClientItem> GetItems()
@@ -153,7 +156,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
DeleteItemData(downloadId); DeleteItemData(downloadId);
} }
_proxy.RemoveTask(ParseDownloadId(downloadId), Settings); _dsTaskProxy.RemoveTask(ParseDownloadId(downloadId), Settings);
_logger.Debug("{0} removed correctly", downloadId); _logger.Debug("{0} removed correctly", downloadId);
} }
@@ -166,7 +169,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{ {
var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings); var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings);
_proxy.AddTaskFromData(fileContent, filename, GetDownloadDirectory(), Settings); _dsTaskProxy.AddTaskFromData(fileContent, filename, GetDownloadDirectory(), Settings);
var items = GetTasks().Where(t => t.Additional.Detail["uri"] == filename); var items = GetTasks().Where(t => t.Additional.Detail["uri"] == filename);
@@ -281,13 +284,13 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
protected ValidationFailure ValidateVersion() protected ValidationFailure ValidateVersion()
{ {
var versionRange = _proxy.GetApiVersion(Settings); var info = _dsTaskProxy.GetApiInfo(Settings);
_logger.Debug("Download Station api version information: Min {0} - Max {1}", versionRange.Min(), versionRange.Max()); _logger.Debug("Download Station api version information: Min {0} - Max {1}", info.MinVersion, info.MaxVersion);
if (!versionRange.Contains(2)) if (info.MinVersion > 2 || info.MaxVersion < 2)
{ {
return new ValidationFailure(string.Empty, $"Download Station API version not supported, should be at least 2. It supports from {versionRange.Min()} to {versionRange.Max()}"); return new ValidationFailure(string.Empty, $"Download Station API version not supported, should be at least 2. It supports from {info.MinVersion} to {info.MaxVersion}");
} }
return null; return null;
@@ -399,7 +402,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
protected string GetDefaultDir() protected string GetDefaultDir()
{ {
var config = _proxy.GetConfig(Settings); var config = _dsInfoProxy.GetConfig(Settings);
var path = config["default_destination"] as string; var path = config["default_destination"] as string;

View File

@@ -0,0 +1,7 @@
namespace NzbDrone.Core.Download.Clients.Sabnzbd.Responses
{
public class SabnzbdFullStatusResponse
{
public SabnzbdFullStatus Status { get; set; }
}
}

View File

@@ -225,10 +225,18 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
if (!completeDir.IsRooted) if (!completeDir.IsRooted)
{ {
var queue = _proxy.GetQueue(0, 1, Settings); if (HasVersion(2, 0))
var defaultRootFolder = new OsPath(queue.DefaultRootFolder); {
var status = _proxy.GetFullStatus(Settings);
completeDir = new OsPath(status.CompleteDir);
}
else
{
var queue = _proxy.GetQueue(0, 1, Settings);
var defaultRootFolder = new OsPath(queue.DefaultRootFolder);
completeDir = defaultRootFolder + completeDir; completeDir = defaultRootFolder + completeDir;
}
} }
foreach (var category in config.Categories) foreach (var category in config.Categories)
@@ -448,50 +456,47 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
}; };
} }
} }
if (config.Misc.enable_tv_sorting && ContainsCategory(config.Misc.tv_categories, Settings.TvCategory))
if (config.Misc.enable_tv_sorting)
{ {
if (!config.Misc.tv_categories.Any<string>() || return new NzbDroneValidationFailure("TvCategory", "Disable TV Sorting")
config.Misc.tv_categories.Contains(Settings.TvCategory) ||
(Settings.TvCategory.IsNullOrWhiteSpace() && config.Misc.tv_categories.Contains("Default")))
{ {
return new NzbDroneValidationFailure("TvCategory", "Disable TV Sorting") InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/sorting/", Settings.Host, Settings.Port),
{ DetailedDescription = "You must disable Sabnzbd TV Sorting for the category Radarr uses to prevent import issues. Go to Sabnzbd to fix it."
InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/sorting/", Settings.Host, Settings.Port), };
DetailedDescription = "You must disable Sabnzbd TV Sorting for the category Radarr uses to prevent import issues. Go to Sabnzbd to fix it."
};
}
} }
if (config.Misc.enable_movie_sorting && ContainsCategory(config.Misc.movie_categories, Settings.TvCategory))
if (config.Misc.enable_movie_sorting)
{ {
if (!config.Misc.movie_categories.Any<string>() || return new NzbDroneValidationFailure("TvCategory", "Disable Movie Sorting")
config.Misc.movie_categories.Contains(Settings.TvCategory) ||
(Settings.TvCategory.IsNullOrWhiteSpace() && config.Misc.movie_categories.Contains("Default")))
{ {
return new NzbDroneValidationFailure("TvCategory", "Disable Movie Sorting") InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/sorting/", Settings.Host, Settings.Port),
{ DetailedDescription = "You must disable Sabnzbd Movie Sorting for the category Radarr uses to prevent import issues. Go to Sabnzbd to fix it."
InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/sorting/", Settings.Host, Settings.Port), };
DetailedDescription = "You must disable Sabnzbd Movie Sorting for the category Radarr uses to prevent import issues. Go to Sabnzbd to fix it."
};
}
} }
if (config.Misc.enable_date_sorting && ContainsCategory(config.Misc.date_categories, Settings.TvCategory))
if (config.Misc.enable_date_sorting)
{ {
if (!config.Misc.date_categories.Any<string>() || return new NzbDroneValidationFailure("TvCategory", "Disable Date Sorting")
config.Misc.date_categories.Contains(Settings.TvCategory) ||
(Settings.TvCategory.IsNullOrWhiteSpace() && config.Misc.date_categories.Contains("Default")))
{ {
return new NzbDroneValidationFailure("TvCategory", "Disable Date Sorting") InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/sorting/", Settings.Host, Settings.Port),
{ DetailedDescription = "You must disable Sabnzbd Date Sorting for the category Radarr uses to prevent import issues. Go to Sabnzbd to fix it."
InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/sorting/", Settings.Host, Settings.Port), };
DetailedDescription = "You must disable Sabnzbd Date Sorting for the category Radarr uses to prevent import issues. Go to Sabnzbd to fix it."
};
}
} }
return null; return null;
} }
private bool ContainsCategory(IEnumerable<string> categories, string category)
{
if (categories == null || categories.Empty())
{
return true;
}
if (category.IsNullOrWhiteSpace())
{
category = "Default";
}
return categories.Contains(category);
}
} }
} }

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public class SabnzbdFullStatus
{
// Added in Sabnzbd 2.0.0, my_home was previously in &mode=queue.
// This is the already resolved completedir path.
[JsonProperty(PropertyName = "completedir")]
public string CompleteDir { get; set; }
}
}

View File

@@ -15,6 +15,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
void RemoveFrom(string source, string id,bool deleteData, SabnzbdSettings settings); void RemoveFrom(string source, string id,bool deleteData, SabnzbdSettings settings);
string GetVersion(SabnzbdSettings settings); string GetVersion(SabnzbdSettings settings);
SabnzbdConfig GetConfig(SabnzbdSettings settings); SabnzbdConfig GetConfig(SabnzbdSettings settings);
SabnzbdFullStatus GetFullStatus(SabnzbdSettings settings);
SabnzbdQueue GetQueue(int start, int limit, SabnzbdSettings settings); SabnzbdQueue GetQueue(int start, int limit, SabnzbdSettings settings);
SabnzbdHistory GetHistory(int start, int limit, string category, SabnzbdSettings settings); SabnzbdHistory GetHistory(int start, int limit, string category, SabnzbdSettings settings);
string RetryDownload(string id, SabnzbdSettings settings); string RetryDownload(string id, SabnzbdSettings settings);
@@ -37,7 +38,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
request.AddQueryParam("cat", category); request.AddQueryParam("cat", category);
request.AddQueryParam("priority", priority); request.AddQueryParam("priority", priority);
request.AddFormUpload("name", filename, nzbData, "application/x-nzb"); request.AddFormUpload("name", filename, nzbData, "application/x-nzb");
SabnzbdAddResponse response; SabnzbdAddResponse response;
@@ -84,6 +85,16 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
return response.Config; return response.Config;
} }
public SabnzbdFullStatus GetFullStatus(SabnzbdSettings settings)
{
var request = BuildRequest("fullstatus", settings);
request.AddQueryParam("skip_dashboard", "1");
var response = Json.Deserialize<SabnzbdFullStatusResponse>(ProcessRequest(request, settings));
return response.Status;
}
public SabnzbdQueue GetQueue(int start, int limit, SabnzbdSettings settings) public SabnzbdQueue GetQueue(int start, int limit, SabnzbdSettings settings)
{ {
var request = BuildRequest("queue", settings); var request = BuildRequest("queue", settings);

View File

@@ -5,6 +5,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
{ {
public class SabnzbdQueue public class SabnzbdQueue
{ {
// Removed in Sabnzbd 2.0.0, see mode=fullstatus instead.
[JsonProperty(PropertyName = "my_home")] [JsonProperty(PropertyName = "my_home")]
public string DefaultRootFolder { get; set; } public string DefaultRootFolder { get; set; }

View File

@@ -49,52 +49,36 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink) protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
{ {
_proxy.AddTorrentFromUrl(magnetLink, Settings); _proxy.AddTorrentFromUrl(magnetLink, Settings.MovieCategory, RTorrentPriority.Normal, Settings.MovieDirectory, Settings);
// Download the magnet to the appropriate directory.
_proxy.SetTorrentLabel(hash, Settings.MovieCategory, Settings);
SetDownloadDirectory(hash);
_proxy.StartTorrent(hash, Settings);
// Wait for the magnet to be resolved.
var tries = 10; var tries = 10;
var retryDelay = 500; var retryDelay = 500;
if (WaitForTorrent(hash, tries, retryDelay))
{ // Wait a bit for the magnet to be resolved.
_logger.Info("Resolved magnet for {0}", remoteMovie.Movie.CleanTitle); if (!WaitForTorrent(hash, tries, retryDelay))
SetDownloadDirectory(hash);
_proxy.SetTorrentLabel(hash, Settings.MovieCategory, Settings);
_proxy.StartTorrent(hash, Settings);
return hash;
}
else
{ {
_logger.Warn("rTorrent could not resolve magnet within {0} seconds, download may remain stuck: {1}.", tries * retryDelay / 1000, magnetLink); _logger.Warn("rTorrent could not resolve magnet within {0} seconds, download may remain stuck: {1}.", tries * retryDelay / 1000, magnetLink);
return hash; return hash;
} }
return hash;
} }
protected override string AddFromTorrentFile(RemoteMovie remoteMovie, string hash, string filename, byte[] fileContent) protected override string AddFromTorrentFile(RemoteMovie remoteMovie, string hash, string filename, byte[] fileContent)
{ {
_proxy.AddTorrentFromFile(filename, fileContent, Settings); _proxy.AddTorrentFromFile(filename, fileContent, Settings.MovieCategory, RTorrentPriority.Normal, Settings.MovieDirectory, Settings);
var tries = 5; var tries = 10;
var retryDelay = 200; var retryDelay = 500;
if (WaitForTorrent(hash, tries, retryDelay)) if (!WaitForTorrent(hash, tries, retryDelay))
{ {
_proxy.SetTorrentLabel(hash, Settings.MovieCategory, Settings); _logger.Debug("rTorrent didn't add the torrent within {0} seconds: {1}.", tries * retryDelay / 1000, filename);
SetDownloadDirectory(hash);
_proxy.StartTorrent(hash, Settings);
return hash;
}
else
{
_logger.Debug("rTorrent could not add file");
RemoveItem(hash, true);
throw new ReleaseDownloadException(remoteMovie.Release, "Downloading torrent failed"); throw new ReleaseDownloadException(remoteMovie.Release, "Downloading torrent failed");
} }
return hash;
} }
public override string Name => "rTorrent"; public override string Name => "rTorrent";
@@ -233,14 +217,6 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
return result.Errors.First(); return result.Errors.First();
} }
private void SetDownloadDirectory(string hash)
{
if (Settings.MovieDirectory.IsNotNullOrWhiteSpace())
{
_proxy.SetTorrentDownloadDirectory(hash, Settings.MovieDirectory, Settings);
}
}
private bool WaitForTorrent(string hash, int tries, int retryDelay) private bool WaitForTorrent(string hash, int tries, int retryDelay)
{ {
for (var i = 0; i < tries; i++) for (var i = 0; i < tries; i++)

View File

@@ -13,15 +13,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
string GetVersion(RTorrentSettings settings); string GetVersion(RTorrentSettings settings);
List<RTorrentTorrent> GetTorrents(RTorrentSettings settings); List<RTorrentTorrent> GetTorrents(RTorrentSettings settings);
void AddTorrentFromUrl(string torrentUrl, RTorrentSettings settings); void AddTorrentFromUrl(string torrentUrl, string label, RTorrentPriority priority, string directory, RTorrentSettings settings);
void AddTorrentFromFile(string fileName, byte[] fileContent, RTorrentSettings settings); void AddTorrentFromFile(string fileName, byte[] fileContent, string label, RTorrentPriority priority, string directory, RTorrentSettings settings);
void RemoveTorrent(string hash, RTorrentSettings settings); void RemoveTorrent(string hash, RTorrentSettings settings);
void SetTorrentPriority(string hash, RTorrentPriority priority, RTorrentSettings settings);
void SetTorrentLabel(string hash, string label, RTorrentSettings settings);
void SetTorrentDownloadDirectory(string hash, string directory, RTorrentSettings settings);
bool HasHashTorrent(string hash, RTorrentSettings settings); bool HasHashTorrent(string hash, RTorrentSettings settings);
void StartTorrent(string hash, RTorrentSettings settings);
void SetDeferredMagnetProperties(string hash, string category, string directory, RTorrentPriority priority, RTorrentSettings settings);
} }
public interface IRTorrent : IXmlRpcProxy public interface IRTorrent : IXmlRpcProxy
@@ -29,35 +24,20 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
[XmlRpcMethod("d.multicall2")] [XmlRpcMethod("d.multicall2")]
object[] TorrentMulticall(params string[] parameters); object[] TorrentMulticall(params string[] parameters);
[XmlRpcMethod("load.normal")] [XmlRpcMethod("load.start")]
int LoadUrl(string target, string data); int LoadStart(string target, string data, params string[] commands);
[XmlRpcMethod("load.raw")] [XmlRpcMethod("load.raw_start")]
int LoadBinary(string target, byte[] data); int LoadRawStart(string target, byte[] data, params string[] commands);
[XmlRpcMethod("d.erase")] [XmlRpcMethod("d.erase")]
int Remove(string hash); int Remove(string hash);
[XmlRpcMethod("d.custom1.set")]
string SetLabel(string hash, string label);
[XmlRpcMethod("d.priority.set")]
int SetPriority(string hash, long priority);
[XmlRpcMethod("d.directory.set")]
int SetDirectory(string hash, string directory);
[XmlRpcMethod("method.set_key")]
int SetKey(string target, string key, string cmd_key, string value);
[XmlRpcMethod("d.name")] [XmlRpcMethod("d.name")]
string GetName(string hash); string GetName(string hash);
[XmlRpcMethod("system.client_version")] [XmlRpcMethod("system.client_version")]
string GetVersion(); string GetVersion();
[XmlRpcMethod("system.multicall")]
object[] SystemMulticall(object[] parameters);
} }
public class RTorrentProxy : IRTorrentProxy public class RTorrentProxy : IRTorrentProxy
@@ -101,20 +81,20 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
var items = new List<RTorrentTorrent>(); var items = new List<RTorrentTorrent>();
foreach (object[] torrent in ret) foreach (object[] torrent in ret)
{ {
var labelDecoded = System.Web.HttpUtility.UrlDecode((string) torrent[3]); var labelDecoded = System.Web.HttpUtility.UrlDecode((string)torrent[3]);
var item = new RTorrentTorrent(); var item = new RTorrentTorrent();
item.Name = (string) torrent[0]; item.Name = (string)torrent[0];
item.Hash = (string) torrent[1]; item.Hash = (string)torrent[1];
item.Path = (string) torrent[2]; item.Path = (string)torrent[2];
item.Category = labelDecoded; item.Category = labelDecoded;
item.TotalSize = (long) torrent[4]; item.TotalSize = (long)torrent[4];
item.RemainingSize = (long) torrent[5]; item.RemainingSize = (long)torrent[5];
item.DownRate = (long) torrent[6]; item.DownRate = (long)torrent[6];
item.Ratio = (long) torrent[7]; item.Ratio = (long)torrent[7];
item.IsOpen = Convert.ToBoolean((long) torrent[8]); item.IsOpen = Convert.ToBoolean((long)torrent[8]);
item.IsActive = Convert.ToBoolean((long) torrent[9]); item.IsActive = Convert.ToBoolean((long)torrent[9]);
item.IsFinished = Convert.ToBoolean((long) torrent[10]); item.IsFinished = Convert.ToBoolean((long)torrent[10]);
items.Add(item); items.Add(item);
} }
@@ -122,26 +102,26 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
return items; return items;
} }
public void AddTorrentFromUrl(string torrentUrl, RTorrentSettings settings) public void AddTorrentFromUrl(string torrentUrl, string label, RTorrentPriority priority, string directory, RTorrentSettings settings)
{ {
_logger.Debug("Executing remote method: load.normal"); _logger.Debug("Executing remote method: load.normal");
var client = BuildClient(settings); var client = BuildClient(settings);
var response = client.LoadUrl("", torrentUrl); var response = client.LoadStart("", torrentUrl, GetCommands(label, priority, directory));
if (response != 0) if (response != 0)
{ {
throw new DownloadClientException("Could not add torrent: {0}.", torrentUrl); throw new DownloadClientException("Could not add torrent: {0}.", torrentUrl);
} }
} }
public void AddTorrentFromFile(string fileName, byte[] fileContent, RTorrentSettings settings) public void AddTorrentFromFile(string fileName, byte[] fileContent, string label, RTorrentPriority priority, string directory, RTorrentSettings settings)
{ {
_logger.Debug("Executing remote method: load.raw"); _logger.Debug("Executing remote method: load.raw");
var client = BuildClient(settings); var client = BuildClient(settings);
var response = client.LoadBinary("", fileContent); var response = client.LoadRawStart("", fileContent, GetCommands(label, priority, directory));
if (response != 0) if (response != 0)
{ {
throw new DownloadClientException("Could not add torrent: {0}.", fileName); throw new DownloadClientException("Could not add torrent: {0}.", fileName);
@@ -161,94 +141,26 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
} }
} }
public void SetTorrentPriority(string hash, RTorrentPriority priority, RTorrentSettings settings) private string[] GetCommands(string label, RTorrentPriority priority, string directory)
{ {
_logger.Debug("Executing remote method: d.priority.set"); var result = new List<string>();
var client = BuildClient(settings); if (label.IsNotNullOrWhiteSpace())
var response = client.SetPriority(hash, (long) priority);
if (response != 0)
{ {
throw new DownloadClientException("Could not set priority on torrent: {0}.", hash); result.Add("d.custom1.set=" + label);
}
}
public void SetTorrentLabel(string hash, string label, RTorrentSettings settings)
{
_logger.Debug("Executing remote method: d.custom1.set");
var labelEncoded = System.Web.HttpUtility.UrlEncode(label);
var client = BuildClient(settings);
var setLabel = client.SetLabel(hash, labelEncoded);
if (setLabel != labelEncoded)
{
throw new DownloadClientException("Could set label on torrent: {0}.", hash);
}
}
public void SetTorrentDownloadDirectory(string hash, string directory, RTorrentSettings settings)
{
_logger.Debug("Executing remote method: d.directory.set");
var client = BuildClient(settings);
var response = client.SetDirectory(hash, directory);
if (response != 0)
{
throw new DownloadClientException("Could not set directory for torrent: {0}.", hash);
}
}
public void SetDeferredMagnetProperties(string hash, string category, string directory, RTorrentPriority priority, RTorrentSettings settings)
{
var commands = new List<string>();
if (category.IsNotNullOrWhiteSpace())
{
commands.Add("d.custom1.set=" + category);
}
if (directory.IsNotNullOrWhiteSpace())
{
commands.Add("d.directory.set=" + directory);
} }
if (priority != RTorrentPriority.Normal) if (priority != RTorrentPriority.Normal)
{ {
commands.Add("d.priority.set=" + (long)priority); result.Add("d.priority.set=" + (int)priority);
} }
// Ensure it gets started if the user doesn't have schedule=...,start_tied= if (directory.IsNotNullOrWhiteSpace())
commands.Add("d.open=");
commands.Add("d.start=");
if (commands.Any())
{ {
var key = "event.download.inserted_new"; result.Add("d.directory.set=" + directory);
var cmd_key = "sonarr_deferred_" + hash;
commands.Add(string.Format("print=\"Applying deferred properties to {0}\"", hash));
// Remove event handler once triggered.
commands.Add(string.Format("\"method.set_key={0},{1}\"", key, cmd_key));
var setKeyValue = string.Format("branch=\"equal=d.hash=,cat={0}\",{{{1}}}", hash, string.Join(",", commands));
_logger.Debug("Executing remote method: method.set_key = {0},{1},{2}", key, cmd_key, setKeyValue);
var client = BuildClient(settings);
// Commands need a target, in this case the target is an empty string
// See: https://github.com/rakshasa/rtorrent/issues/227
var response = client.SetKey("", key, cmd_key, setKeyValue);
if (response != 0)
{
throw new DownloadClientException("Could set properties for torrent: {0}.", hash);
}
} }
return result.ToArray();
} }
public bool HasHashTorrent(string hash, RTorrentSettings settings) public bool HasHashTorrent(string hash, RTorrentSettings settings)
@@ -270,32 +182,6 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
} }
} }
public void StartTorrent(string hash, RTorrentSettings settings)
{
_logger.Debug("Executing remote methods: d.open and d.start");
var client = BuildClient(settings);
var multicallResponse = client.SystemMulticall(new[]
{
new
{
methodName = "d.open",
@params = new[] { hash }
},
new
{
methodName = "d.start",
@params = new[] { hash }
},
}).SelectMany(c => ((IEnumerable<int>)c));
if (multicallResponse.Any(r => r != 0))
{
throw new DownloadClientException("Could not start torrent: {0}.", hash);
}
}
private IRTorrent BuildClient(RTorrentSettings settings) private IRTorrent BuildClient(RTorrentSettings settings)
{ {
var client = XmlRpcProxyGen.Create<IRTorrent>(); var client = XmlRpcProxyGen.Create<IRTorrent>();
@@ -316,4 +202,4 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
return client; return client;
} }
} }
} }

View File

@@ -129,7 +129,14 @@ namespace NzbDrone.Core.Download
{ {
var statusMessages = importResults var statusMessages = importResults
.Where(v => v.Result != ImportResultType.Imported) .Where(v => v.Result != ImportResultType.Imported)
.Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalMovie.Path), v.Errors)) .Select(v =>
{
if (v.ImportDecision.LocalMovie == null)
{
return new TrackedDownloadStatusMessage("", v.Errors);
}
return new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalMovie.Path), v.Errors);
})
.ToArray(); .ToArray();
trackedDownload.Warn(statusMessages); trackedDownload.Warn(statusMessages);

View File

@@ -111,7 +111,7 @@ namespace NzbDrone.Core.Download
public ValidationResult Test() public ValidationResult Test()
{ {
var failures = new List<ValidationFailure>(); var failures = new List<ValidationFailure>();
try try
{ {
Test(failures); Test(failures);

View File

@@ -117,45 +117,43 @@ namespace NzbDrone.Core.Download.Pending
foreach (var pendingRelease in GetPendingReleases()) foreach (var pendingRelease in GetPendingReleases())
{ {
//foreach (var episode in pendingRelease.RemoteEpisode.Episodes) var ect = pendingRelease.Release.PublishDate.AddMinutes(GetDelay(pendingRelease.RemoteMovie));
//{
var ect = pendingRelease.Release.PublishDate.AddMinutes(GetDelay(pendingRelease.RemoteMovie));
if (ect < nextRssSync.Value) if (ect < nextRssSync.Value)
{ {
ect = nextRssSync.Value; ect = nextRssSync.Value;
} }
else else
{ {
ect = ect.AddMinutes(_configService.RssSyncInterval); ect = ect.AddMinutes(_configService.RssSyncInterval);
} }
var queue = new Queue.Queue var queue = new Queue.Queue
{ {
Id = GetQueueId(pendingRelease, pendingRelease.RemoteMovie.Movie), Id = GetQueueId(pendingRelease, pendingRelease.RemoteMovie.Movie),
Series = null, Series = null,
Episode = null, Episode = null,
Movie = pendingRelease.RemoteMovie.Movie, Movie = pendingRelease.RemoteMovie.Movie,
Quality = pendingRelease.RemoteMovie.ParsedMovieInfo.Quality, Quality = pendingRelease.RemoteMovie.ParsedMovieInfo?.Quality ?? new QualityModel(),
Title = pendingRelease.Title, Title = pendingRelease.Title,
Size = pendingRelease.RemoteMovie.Release.Size, Size = pendingRelease.RemoteMovie.Release.Size,
Sizeleft = pendingRelease.RemoteMovie.Release.Size, Sizeleft = pendingRelease.RemoteMovie.Release.Size,
RemoteMovie = pendingRelease.RemoteMovie, RemoteMovie = pendingRelease.RemoteMovie,
Timeleft = ect.Subtract(DateTime.UtcNow), Timeleft = ect.Subtract(DateTime.UtcNow),
EstimatedCompletionTime = ect, EstimatedCompletionTime = ect,
Status = "Pending", Status = "Pending",
Protocol = pendingRelease.RemoteMovie.Release.DownloadProtocol Protocol = pendingRelease.RemoteMovie.Release.DownloadProtocol
}; };
queued.Add(queue);
//} queued.Add(queue);
} }
//Return best quality release for each episode //Return best quality release for each episode
var deduped = queued.GroupBy(q => q.Episode.Id).Select(g => var deduped = queued.GroupBy(q => q.Movie.Id).Select(g =>
{ {
var series = g.First().Series; var movies = g.First().Movie;
return g.OrderByDescending(e => e.Quality, new QualityModelComparer(series.Profile)) return g.OrderByDescending(e => e.Quality, new QualityModelComparer(movies.Profile))
.ThenBy(q => PrioritizeDownloadProtocol(q.Movie, q.Protocol)) .ThenBy(q => PrioritizeDownloadProtocol(q.Movie, q.Protocol))
.First(); .First();
}); });
@@ -220,14 +218,20 @@ namespace NzbDrone.Core.Download.Pending
private void Insert(DownloadDecision decision) private void Insert(DownloadDecision decision)
{ {
_repository.Insert(new PendingRelease var release = new PendingRelease
{ {
MovieId = decision.RemoteMovie.Movie.Id, MovieId = decision.RemoteMovie.Movie.Id,
ParsedMovieInfo = decision.RemoteMovie.ParsedMovieInfo, ParsedMovieInfo = decision.RemoteMovie.ParsedMovieInfo,
Release = decision.RemoteMovie.Release, Release = decision.RemoteMovie.Release,
Title = decision.RemoteMovie.Release.Title, Title = decision.RemoteMovie.Release.Title,
Added = DateTime.UtcNow Added = DateTime.UtcNow
}); };
if (release.ParsedMovieInfo == null)
{
_logger.Warn("Pending release {0} does not have ParsedMovieInfo, will cause issues.", release.Title);
}
_repository.Insert(release);
_eventAggregator.PublishEvent(new PendingReleasesUpdatedEvent()); _eventAggregator.PublishEvent(new PendingReleasesUpdatedEvent());
} }
@@ -254,12 +258,12 @@ namespace NzbDrone.Core.Download.Pending
return new[] { delay, minimumAge }.Max(); return new[] { delay, minimumAge }.Max();
} }
private void RemoveGrabbed(RemoteMovie remoteEpisode) private void RemoveGrabbed(RemoteMovie remoteMovie)
{ {
var pendingReleases = GetPendingReleases(); var pendingReleases = GetPendingReleases();
var existingReports = pendingReleases.Where(r => r.RemoteMovie.Movie.Id == remoteEpisode.Movie.Id) var existingReports = pendingReleases.Where(r => r.RemoteMovie.Movie.Id == remoteMovie.Movie.Id)
.ToList(); .ToList();
if (existingReports.Empty()) if (existingReports.Empty())
@@ -267,11 +271,11 @@ namespace NzbDrone.Core.Download.Pending
return; return;
} }
var profile = remoteEpisode.Movie.Profile.Value; var profile = remoteMovie.Movie.Profile.Value;
foreach (var existingReport in existingReports) foreach (var existingReport in existingReports)
{ {
var compare = new QualityModelComparer(profile).Compare(remoteEpisode.ParsedMovieInfo.Quality, var compare = new QualityModelComparer(profile).Compare(remoteMovie.ParsedMovieInfo.Quality,
existingReport.RemoteMovie.ParsedMovieInfo.Quality); existingReport.RemoteMovie.ParsedMovieInfo.Quality);
//Only remove lower/equal quality pending releases //Only remove lower/equal quality pending releases

View File

@@ -15,12 +15,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
{ {
var mapper = _database.GetDataMapper(); var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM EpisodeFiles mapper.ExecuteNonQuery(@"DELETE FROM MovieFiles
WHERE Id IN ( WHERE Id IN (
SELECT EpisodeFiles.Id FROM EpisodeFiles SELECT MovieFiles.Id FROM MovieFiles
LEFT OUTER JOIN Episodes LEFT OUTER JOIN Movies
ON EpisodeFiles.Id = Episodes.EpisodeFileId ON MovieFiles.Id = Movies.MovieFileId
WHERE Episodes.Id IS NULL)"); WHERE Movies.Id IS NULL)");
} }
} }
} }

View File

@@ -19,7 +19,7 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
{ {
var mapper = _database.GetDataMapper(); var mapper = _database.GetDataMapper();
var usedTags = new[] { "Series", "Notifications", "DelayProfiles", "Restrictions" } var usedTags = new[] { "Movies", "Series", "Notifications", "DelayProfiles", "Restrictions" }
.SelectMany(v => GetUsedTags(v, mapper)) .SelectMany(v => GetUsedTags(v, mapper))
.Distinct() .Distinct()
.ToArray(); .ToArray();

View File

@@ -6,21 +6,21 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
{ {
public class UpdateCleanTitleForSeries : IHousekeepingTask public class UpdateCleanTitleForSeries : IHousekeepingTask
{ {
private readonly ISeriesRepository _seriesRepository; private readonly IMovieRepository _movieRepository;
public UpdateCleanTitleForSeries(ISeriesRepository seriesRepository) public UpdateCleanTitleForSeries(IMovieRepository movieRepository)
{ {
_seriesRepository = seriesRepository; _movieRepository = movieRepository;
} }
public void Clean() public void Clean()
{ {
var series = _seriesRepository.All().ToList(); var movies = _movieRepository.All().ToList();
series.ForEach(s => movies.ForEach(m =>
{ {
s.CleanTitle = s.CleanTitle.CleanSeriesTitle(); m.CleanTitle = m.CleanTitle.CleanSeriesTitle();
_seriesRepository.Update(s); _movieRepository.Update(m);
}); });
} }
} }

View File

@@ -1,7 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Exceptions; using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@@ -64,12 +62,19 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
Subtitles = x.Element("subtitles").Value, Subtitles = x.Element("subtitles").Value,
EncodeStatus = x.Element("encodestatus").Value, EncodeStatus = x.Element("encodestatus").Value,
Freeleech = x.Element("freeleech").Value, Freeleech = x.Element("freeleech").Value,
ImdbId = x.Element("imdb").Value
}).ToList(); }).ToList();
foreach (var torrent in torrents) foreach (var torrent in torrents)
{ {
var id = torrent.Id; var id = torrent.Id;
var title = $"{torrent.Name}.{torrent.Year}.{torrent.Resolution}.{torrent.Media}.{torrent.Encoding}.{torrent.AudioFormat}-{torrent.ReleaseGroup}"; var title = $"{torrent.Name}.{torrent.Year}.{torrent.Resolution}.{torrent.Media}.{torrent.Encoding}.{torrent.AudioFormat}-{torrent.ReleaseGroup}";
IndexerFlags flags = 0;
if (torrent.Freeleech == "1.00")
{
flags |= IndexerFlags.G_Freeleech;
}
torrentInfos.Add(new TorrentInfo() torrentInfos.Add(new TorrentInfo()
{ {
@@ -80,7 +85,9 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
InfoUrl = GetInfoUrl(torrent.GroupId, id), InfoUrl = GetInfoUrl(torrent.GroupId, id),
Seeders = int.Parse(torrent.Seeders), Seeders = int.Parse(torrent.Seeders),
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders), Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
PublishDate = torrent.Time.ToUniversalTime() PublishDate = torrent.Time.ToUniversalTime(),
ImdbId = int.Parse(torrent.ImdbId.Substring(2)),
IndexerFlags = flags,
}); });
} }
} }

View File

@@ -101,7 +101,7 @@ namespace NzbDrone.Core.Indexers.HDBits
public class ImdbInfo public class ImdbInfo
{ {
public int? Id { get; set; } public int Id { get; set; }
public string EnglishTitle { get; set; } public string EnglishTitle { get; set; }
public string OriginalTitle { get; set; } public string OriginalTitle { get; set; }
public int? Year { get; set; } public int? Year { get; set; }

View File

@@ -1,8 +1,4 @@
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Indexers.HDBits namespace NzbDrone.Core.Indexers.HDBits
{ {

View File

@@ -53,6 +53,18 @@ namespace NzbDrone.Core.Indexers.HDBits
var id = result.Id; var id = result.Id;
var internalRelease = (result.TypeOrigin == 1 ? true : false); var internalRelease = (result.TypeOrigin == 1 ? true : false);
IndexerFlags flags = 0;
if (result.FreeLeech == "yes")
{
flags |= IndexerFlags.G_Freeleech;
}
if (internalRelease)
{
flags |= IndexerFlags.HDB_Internal;
}
torrentInfos.Add(new HDBitsInfo() torrentInfos.Add(new HDBitsInfo()
{ {
Guid = string.Format("HDBits-{0}", id), Guid = string.Format("HDBits-{0}", id),
@@ -64,7 +76,9 @@ namespace NzbDrone.Core.Indexers.HDBits
Seeders = result.Seeders, Seeders = result.Seeders,
Peers = result.Leechers + result.Seeders, Peers = result.Leechers + result.Seeders,
PublishDate = result.Added.ToUniversalTime(), PublishDate = result.Added.ToUniversalTime(),
Internal = internalRelease Internal = internalRelease,
ImdbId = result.ImdbInfo?.Id ?? 0,
IndexerFlags = flags
}); });
} }

View File

@@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net;
using System.Xml;
using System.Xml.Linq; using System.Xml.Linq;
using NLog; using NLog;
using NzbDrone.Common.Cache; using NzbDrone.Common.Cache;
@@ -49,15 +51,30 @@ namespace NzbDrone.Core.Indexers.Newznab
var request = new HttpRequest(url, HttpAccept.Rss); var request = new HttpRequest(url, HttpAccept.Rss);
HttpResponse response;
try try
{ {
var response = _httpClient.Get(request); response = _httpClient.Get(request);
capabilities = ParseCapabilities(response);
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Debug(ex, string.Format("Failed to get capabilities from {0}: {1}", indexerSettings.Url, ex.Message)); _logger.Debug(ex, "Failed to get newznab api capabilities from {0}", indexerSettings.Url);
throw;
}
try
{
capabilities = ParseCapabilities(response);
}
catch (XmlException ex)
{
_logger.Debug(ex, "Failed to parse newznab api capabilities for {0}.", indexerSettings.Url);
throw;
}
catch (Exception ex)
{
_logger.Error(ex, "Failed to determine newznab api capabilities for {0}, using the defaults instead till Sonarr restarts.", indexerSettings.Url);
} }
return capabilities; return capabilities;

View File

@@ -52,7 +52,7 @@ namespace NzbDrone.Core.Indexers.Newznab
{ {
var pageableRequests = new IndexerPageableRequestChain(); var pageableRequests = new IndexerPageableRequestChain();
if (SupportsMovieSearch) if (SupportsMovieSearch && searchCriteria.Movie.ImdbId.IsNotNullOrWhiteSpace())
{ {
pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "movie", $"&imdbid={searchCriteria.Movie.ImdbId.Substring(2)}")); pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "movie", $"&imdbid={searchCriteria.Movie.ImdbId.Substring(2)}"));
} }

View File

@@ -28,6 +28,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public string ReleaseName { get; set; } public string ReleaseName { get; set; }
public bool Checked { get; set; } public bool Checked { get; set; }
public bool GoldenPopcorn { get; set; } public bool GoldenPopcorn { get; set; }
public string FreeleechType { get; set; }
} }
public class Movie public class Movie

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using Newtonsoft.Json; using Newtonsoft.Json;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
@@ -41,8 +41,8 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
} }
var jsonResponse = JsonConvert.DeserializeObject<PassThePopcornResponse>(indexerResponse.Content); var jsonResponse = JsonConvert.DeserializeObject<PassThePopcornResponse>(indexerResponse.Content);
if (jsonResponse.TotalResults == "0" || if (jsonResponse.TotalResults == "0" ||
jsonResponse.TotalResults.IsNullOrWhiteSpace() || jsonResponse.TotalResults.IsNullOrWhiteSpace() ||
jsonResponse.Movies == null) jsonResponse.Movies == null)
{ {
throw new IndexerException(indexerResponse, "No results were found"); throw new IndexerException(indexerResponse, "No results were found");
@@ -55,58 +55,69 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
{ {
var id = torrent.Id; var id = torrent.Id;
var title = torrent.ReleaseName; var title = torrent.ReleaseName;
IndexerFlags flags = 0;
if (torrent.GoldenPopcorn) if (torrent.GoldenPopcorn)
{ {
title = $"{title} 🍿"; flags |= IndexerFlags.PTP_Golden;//title = $"{title} 🍿";
} }
if (torrent.Checked) if (torrent.Checked)
{ {
title = $"{title} ✔"; flags |= IndexerFlags.PTP_Approved;//title = $"{title} ✔";
}
if (torrent.FreeleechType == "Freeleech")
{
flags |= IndexerFlags.G_Freeleech;
} }
// Only add approved torrents // Only add approved torrents
if (_settings.RequireApproved && torrent.Checked) if (_settings.RequireApproved && torrent.Checked)
{
torrentInfos.Add(new PassThePopcornInfo()
{ {
Guid = string.Format("PassThePopcorn-{0}", id), torrentInfos.Add(new PassThePopcornInfo()
Title = title, {
Size = long.Parse(torrent.Size), Guid = string.Format("PassThePopcorn-{0}", id),
DownloadUrl = GetDownloadUrl(id, jsonResponse.AuthKey, jsonResponse.PassKey), Title = title,
InfoUrl = GetInfoUrl(result.GroupId, id), Size = long.Parse(torrent.Size),
Seeders = int.Parse(torrent.Seeders), DownloadUrl = GetDownloadUrl(id, jsonResponse.AuthKey, jsonResponse.PassKey),
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders), InfoUrl = GetInfoUrl(result.GroupId, id),
PublishDate = torrent.UploadTime.ToUniversalTime(), Seeders = int.Parse(torrent.Seeders),
Golden = torrent.GoldenPopcorn, Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
Scene = torrent.Scene, PublishDate = torrent.UploadTime.ToUniversalTime(),
Approved = torrent.Checked Golden = torrent.GoldenPopcorn,
}); Scene = torrent.Scene,
} Approved = torrent.Checked,
// Add all torrents ImdbId = (result.ImdbId.IsNotNullOrWhiteSpace() ? int.Parse(result.ImdbId) : 0),
else if (!_settings.RequireApproved) IndexerFlags = flags
{ });
torrentInfos.Add(new PassThePopcornInfo() }
// Add all torrents
else if (!_settings.RequireApproved)
{ {
Guid = string.Format("PassThePopcorn-{0}", id), torrentInfos.Add(new PassThePopcornInfo()
Title = title, {
Size = long.Parse(torrent.Size), Guid = string.Format("PassThePopcorn-{0}", id),
DownloadUrl = GetDownloadUrl(id, jsonResponse.AuthKey, jsonResponse.PassKey), Title = title,
InfoUrl = GetInfoUrl(result.GroupId, id), Size = long.Parse(torrent.Size),
Seeders = int.Parse(torrent.Seeders), DownloadUrl = GetDownloadUrl(id, jsonResponse.AuthKey, jsonResponse.PassKey),
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders), InfoUrl = GetInfoUrl(result.GroupId, id),
PublishDate = torrent.UploadTime.ToUniversalTime(), Seeders = int.Parse(torrent.Seeders),
Golden = torrent.GoldenPopcorn, Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
Scene = torrent.Scene, PublishDate = torrent.UploadTime.ToUniversalTime(),
Approved = torrent.Checked Golden = torrent.GoldenPopcorn,
}); Scene = torrent.Scene,
} Approved = torrent.Checked,
// Don't add any torrents ImdbId = (result.ImdbId.IsNotNullOrWhiteSpace() ? int.Parse(result.ImdbId) : 0),
else if (_settings.RequireApproved && !torrent.Checked) IndexerFlags = flags
{ });
continue; }
} // Don't add any torrents
else if (_settings.RequireApproved && !torrent.Checked)
{
continue;
}
} }
} }
@@ -118,10 +129,10 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
return return
torrentInfos.OrderByDescending(o => o.PublishDate) torrentInfos.OrderByDescending(o => o.PublishDate)
.ThenBy(o => ((dynamic)o).Golden ? 0 : 1) .ThenBy(o => ((dynamic)o).Golden ? 0 : 1)
.ThenBy(o => ((dynamic) o).Scene ? 0 : 1) .ThenBy(o => ((dynamic)o).Scene ? 0 : 1)
.ToArray(); .ToArray();
} }
return return
torrentInfos.OrderByDescending(o => o.PublishDate) torrentInfos.OrderByDescending(o => o.PublishDate)
.ThenBy(o => ((dynamic)o).Golden ? 0 : 1) .ThenBy(o => ((dynamic)o).Golden ? 0 : 1)
.ToArray(); .ToArray();
@@ -130,14 +141,14 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
// prefer scene // prefer scene
if (_settings.Scene) if (_settings.Scene)
{ {
return return
torrentInfos.OrderByDescending(o => o.PublishDate) torrentInfos.OrderByDescending(o => o.PublishDate)
.ThenBy(o => ((dynamic)o).Scene ? 0 : 1) .ThenBy(o => ((dynamic)o).Scene ? 0 : 1)
.ToArray(); .ToArray();
} }
// order by date // order by date
return return
torrentInfos torrentInfos
.OrderByDescending(o => o.PublishDate) .OrderByDescending(o => o.PublishDate)
.ToArray(); .ToArray();

View File

@@ -46,6 +46,9 @@ namespace NzbDrone.Core.Indexers.Torznab
torrentInfo.ImdbId = int.Parse(GetImdbId(item).Substring(2)); torrentInfo.ImdbId = int.Parse(GetImdbId(item).Substring(2));
} }
} }
torrentInfo.IndexerFlags = GetFlags(item);
return torrentInfo; return torrentInfo;
} }
@@ -151,6 +154,32 @@ namespace NzbDrone.Core.Indexers.Torznab
return base.GetPeers(item); return base.GetPeers(item);
} }
protected IndexerFlags GetFlags(XElement item)
{
IndexerFlags flags = 0;
var downloadFactor = TryGetFloatTorznabAttribute(item, "downloadvolumefactor", 1);
var uploadFactor = TryGetFloatTorznabAttribute(item, "uploadvolumefactor", 1);
if (uploadFactor == 2)
{
flags |= IndexerFlags.G_DoubleUpload;
}
if (downloadFactor == 0.5)
{
flags |= IndexerFlags.G_Halfleech;
}
if (downloadFactor == 0.0)
{
flags |= IndexerFlags.G_Freeleech;
}
return flags;
}
protected string TryGetTorznabAttribute(XElement item, string key, string defaultValue = "") protected string TryGetTorznabAttribute(XElement item, string key, string defaultValue = "")
{ {
var attr = item.Elements(ns + "attr").FirstOrDefault(e => e.Attribute("name").Value.Equals(key, StringComparison.CurrentCultureIgnoreCase)); var attr = item.Elements(ns + "attr").FirstOrDefault(e => e.Attribute("name").Value.Equals(key, StringComparison.CurrentCultureIgnoreCase));
@@ -160,6 +189,20 @@ namespace NzbDrone.Core.Indexers.Torznab
return attr.Attribute("value").Value; return attr.Attribute("value").Value;
} }
return defaultValue;
}
protected float TryGetFloatTorznabAttribute(XElement item, string key, float defaultValue = 0)
{
var attr = TryGetTorznabAttribute(item, key, defaultValue.ToString());
float result = 0;
if (float.TryParse(attr, out result))
{
return result;
}
return defaultValue; return defaultValue;
} }
} }

View File

@@ -44,6 +44,7 @@ namespace NzbDrone.Core.MediaFiles
private readonly IMediaFileTableCleanupService _mediaFileTableCleanupService; private readonly IMediaFileTableCleanupService _mediaFileTableCleanupService;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly IMovieService _movieService; private readonly IMovieService _movieService;
private readonly IMovieFileRepository _movieFileRepository;
private readonly Logger _logger; private readonly Logger _logger;
public DiskScanService(IDiskProvider diskProvider, public DiskScanService(IDiskProvider diskProvider,
@@ -55,6 +56,7 @@ namespace NzbDrone.Core.MediaFiles
IMediaFileTableCleanupService mediaFileTableCleanupService, IMediaFileTableCleanupService mediaFileTableCleanupService,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
IMovieService movieService, IMovieService movieService,
IMovieFileRepository movieFileRepository,
Logger logger) Logger logger)
{ {
_diskProvider = diskProvider; _diskProvider = diskProvider;
@@ -66,6 +68,7 @@ namespace NzbDrone.Core.MediaFiles
_mediaFileTableCleanupService = mediaFileTableCleanupService; _mediaFileTableCleanupService = mediaFileTableCleanupService;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_movieService = movieService; _movieService = movieService;
_movieFileRepository = movieFileRepository;
_logger = logger; _logger = logger;
} }
@@ -136,14 +139,14 @@ namespace NzbDrone.Core.MediaFiles
if (!_diskProvider.FolderExists(rootFolder)) if (!_diskProvider.FolderExists(rootFolder))
{ {
_logger.Warn("Series' root folder ({0}) doesn't exist.", rootFolder); _logger.Warn("Movies' root folder ({0}) doesn't exist.", rootFolder);
_eventAggregator.PublishEvent(new MovieScanSkippedEvent(movie, MovieScanSkippedReason.RootFolderDoesNotExist)); _eventAggregator.PublishEvent(new MovieScanSkippedEvent(movie, MovieScanSkippedReason.RootFolderDoesNotExist));
return; return;
} }
if (_diskProvider.GetDirectories(rootFolder).Empty()) if (_diskProvider.GetDirectories(rootFolder).Empty())
{ {
_logger.Warn("Series' root folder ({0}) is empty.", rootFolder); _logger.Warn("Movies' root folder ({0}) is empty.", rootFolder);
_eventAggregator.PublishEvent(new MovieScanSkippedEvent(movie, MovieScanSkippedReason.RootFolderIsEmpty)); _eventAggregator.PublishEvent(new MovieScanSkippedEvent(movie, MovieScanSkippedReason.RootFolderIsEmpty));
return; return;
} }
@@ -155,13 +158,20 @@ namespace NzbDrone.Core.MediaFiles
if (_configService.CreateEmptySeriesFolders && if (_configService.CreateEmptySeriesFolders &&
_diskProvider.FolderExists(rootFolder)) _diskProvider.FolderExists(rootFolder))
{ {
_logger.Debug("Creating missing series folder: {0}", movie.Path); _logger.Debug("Creating missing movies folder: {0}", movie.Path);
_diskProvider.CreateFolder(movie.Path); _diskProvider.CreateFolder(movie.Path);
SetPermissions(movie.Path); SetPermissions(movie.Path);
} }
else else
{ {
_logger.Debug("Series folder doesn't exist: {0}", movie.Path); // Delete Movie from MovieFiles
_movieFileRepository.Delete(movie.MovieFileId);
// Update Movie
movie.MovieFileId = 0;
_movieService.UpdateMovie(movie);
_logger.Debug("Movies folder doesn't exist: {0}", movie.Path);
} }
_eventAggregator.PublishEvent(new MovieScanSkippedEvent(movie, MovieScanSkippedReason.MovieFolderDoesNotExist)); _eventAggregator.PublishEvent(new MovieScanSkippedEvent(movie, MovieScanSkippedReason.MovieFolderDoesNotExist));

View File

@@ -135,16 +135,16 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
localMovie.Size = _diskProvider.GetFileSize(file); localMovie.Size = _diskProvider.GetFileSize(file);
_logger.Debug("Size: {0}", localMovie.Size); _logger.Debug("Size: {0}", localMovie.Size);
var current = localMovie.Quality;
//TODO: make it so media info doesn't ruin the import process of a new series //TODO: make it so media info doesn't ruin the import process of a new series
if (sceneSource) if (sceneSource && ShouldCheckQualityForParsedQuality(current.Quality))
{ {
localMovie.MediaInfo = _videoFileInfoReader.GetMediaInfo(file); localMovie.MediaInfo = _videoFileInfoReader.GetMediaInfo(file);
if (shouldCheckQuality) if (shouldCheckQuality)
{ {
_logger.Debug("Checking quality for this video file to make sure nothing mismatched."); _logger.Debug("Checking quality for this video file to make sure nothing mismatched.");
var width = localMovie.MediaInfo.Width; var width = localMovie.MediaInfo.Width;
var current = localMovie.Quality;
var qualityName = current.Quality.Name.ToLower(); var qualityName = current.Quality.Name.ToLower();
QualityModel updated = null; QualityModel updated = null;
if (width > 2000) if (width > 2000)
@@ -565,5 +565,20 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
return false; return false;
} }
private bool ShouldCheckQualityForParsedQuality(Quality quality)
{
List<Quality> shouldNotCheck = new List<Quality> { Quality.WORKPRINT, Quality.TELECINE, Quality.TELESYNC,
Quality.DVDSCR, Quality.DVD, Quality.CAM, Quality.DVDR, Quality.Remux1080p, Quality.Remux2160p, Quality.REGIONAL
};
if (shouldNotCheck.Contains(quality))
{
return false;
}
return true;
}
} }
} }

View File

@@ -189,6 +189,11 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
public int Open(Stream stream) public int Open(Stream stream)
{ {
if (stream.Length < 1024)
{
return 0;
}
var isValid = (int)MediaInfo_Open_Buffer_Init(_handle, stream.Length, 0); var isValid = (int)MediaInfo_Open_Buffer_Init(_handle, stream.Length, 0);
if (isValid == 1) if (isValid == 1)
{ {
@@ -203,7 +208,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
totalRead += bufferRead; totalRead += bufferRead;
var status = (BufferStatus)MediaInfo_Open_Buffer_Continue(_handle, buffer, (IntPtr)bufferRead); var status = (BufferStatus)MediaInfo_Open_Buffer_Continue(_handle, buffer, (IntPtr)bufferRead);
if (status.HasFlag(BufferStatus.Finalized) || status <= 0 || bufferRead == 0) if (status.HasFlag(BufferStatus.Finalized) || status <= 0 || bufferRead == 0)
{ {
Logger.Trace("Read file offset {0}-{1} ({2} bytes)", seekStart, stream.Position, stream.Position - seekStart); Logger.Trace("Read file offset {0}-{1} ({2} bytes)", seekStart, stream.Position, stream.Position - seekStart);

View File

@@ -62,6 +62,7 @@ namespace NzbDrone.Core.MetadataSource.PreDB
private List<PreDBResult> GetResults(string category = "", string search = "") private List<PreDBResult> GetResults(string category = "", string search = "")
{ {
return new List<PreDBResult>();
var builder = new HttpRequestBuilder("http://predb.me").AddQueryParam("rss", "1"); var builder = new HttpRequestBuilder("http://predb.me").AddQueryParam("rss", "1");
if (category.IsNotNullOrWhiteSpace()) if (category.IsNotNullOrWhiteSpace())
{ {
@@ -171,10 +172,12 @@ namespace NzbDrone.Core.MetadataSource.PreDB
public bool HasReleases(Movie movie) public bool HasReleases(Movie movie)
{ {
var results = GetResults("movies", movie.Title); try
{
var results = GetResults("movies", movie.Title);
foreach (PreDBResult result in results) foreach (PreDBResult result in results)
{ {
var parsed = Parser.Parser.ParseMovieTitle(result.Title); var parsed = Parser.Parser.ParseMovieTitle(result.Title);
if (parsed == null) if (parsed == null)
{ {
@@ -182,13 +185,20 @@ namespace NzbDrone.Core.MetadataSource.PreDB
} }
var match = _parsingService.Map(parsed, "", new MovieSearchCriteria { Movie = movie }); var match = _parsingService.Map(parsed, "", new MovieSearchCriteria { Movie = movie });
if (match != null && match.Movie != null && match.Movie.Id == movie.Id) if (match != null && match.Movie != null && match.Movie.Id == movie.Id)
{ {
return true; return true;
} }
} }
return false; return false;
}
catch (Exception ex)
{
_logger.Warn(ex, "Error while looking on predb.me.");
return false;
}
} }
} }
} }

View File

@@ -84,9 +84,18 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
.Build(); .Build();
request.AllowAutoRedirect = true; request.AllowAutoRedirect = true;
request.SuppressHttpError = true; // request.SuppressHttpError = true;
var response = _httpClient.Get<MovieResourceRoot>(request); var response = _httpClient.Get<MovieResourceRoot>(request);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new HttpException(request, response);
}
if (response.Headers.ContentType != HttpAccept.JsonCharset.Value)
{
throw new HttpException(request, response);
}
// The dude abides, so should us, Lets be nice to TMDb // The dude abides, so should us, Lets be nice to TMDb
// var allowed = int.Parse(response.Headers.GetValues("X-RateLimit-Limit").First()); // get allowed // var allowed = int.Parse(response.Headers.GetValues("X-RateLimit-Limit").First()); // get allowed
@@ -301,19 +310,28 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
return movie; return movie;
} }
public Movie GetMovieInfo(string ImdbId) public Movie GetMovieInfo(string imdbId)
{ {
var request = _movieBuilder.Create() var request = _movieBuilder.Create()
.SetSegment("route", "find") .SetSegment("route", "find")
.SetSegment("id", ImdbId) .SetSegment("id", imdbId)
.SetSegment("secondaryRoute", "") .SetSegment("secondaryRoute", "")
.AddQueryParam("external_source", "imdb_id") .AddQueryParam("external_source", "imdb_id")
.Build(); .Build();
request.AllowAutoRedirect = true; request.AllowAutoRedirect = true;
request.SuppressHttpError = true; // request.SuppressHttpError = true;
var response = _httpClient.Get<FindRoot>(request); var response = _httpClient.Get<FindRoot>(request);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new HttpException(request, response);
}
if (response.Headers.ContentType != HttpAccept.JsonCharset.Value)
{
throw new HttpException(request, response);
}
// The dude abides, so should us, Lets be nice to TMDb // The dude abides, so should us, Lets be nice to TMDb
// var allowed = int.Parse(response.Headers.GetValues("X-RateLimit-Limit").First()); // get allowed // var allowed = int.Parse(response.Headers.GetValues("X-RateLimit-Limit").First()); // get allowed
@@ -711,39 +729,47 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
public Movie MapMovieToTmdbMovie(Movie movie) public Movie MapMovieToTmdbMovie(Movie movie)
{ {
Movie newMovie = movie; try
if (movie.TmdbId > 0) {
{ Movie newMovie = movie;
newMovie = GetMovieInfo(movie.TmdbId); if (movie.TmdbId > 0)
} {
else if (movie.ImdbId.IsNotNullOrWhiteSpace()) newMovie = GetMovieInfo(movie.TmdbId);
{ }
newMovie = GetMovieInfo(movie.ImdbId); else if (movie.ImdbId.IsNotNullOrWhiteSpace())
} {
else newMovie = GetMovieInfo(movie.ImdbId);
{ }
var yearStr = ""; else
if (movie.Year > 1900) {
{ var yearStr = "";
yearStr = $" {movie.Year}"; if (movie.Year > 1900)
} {
newMovie = SearchForNewMovie(movie.Title + yearStr).FirstOrDefault(); yearStr = $" {movie.Year}";
} }
newMovie = SearchForNewMovie(movie.Title + yearStr).FirstOrDefault();
}
if (newMovie == null) if (newMovie == null)
{ {
_logger.Warn("Couldn't map movie {0} to a movie on The Movie DB. It will not be added :(", movie.Title); _logger.Warn("Couldn't map movie {0} to a movie on The Movie DB. It will not be added :(", movie.Title);
return null; return null;
} }
newMovie.Path = movie.Path; newMovie.Path = movie.Path;
newMovie.RootFolderPath = movie.RootFolderPath; newMovie.RootFolderPath = movie.RootFolderPath;
newMovie.ProfileId = movie.ProfileId; newMovie.ProfileId = movie.ProfileId;
newMovie.Monitored = movie.Monitored; newMovie.Monitored = movie.Monitored;
newMovie.MovieFile = movie.MovieFile; newMovie.MovieFile = movie.MovieFile;
newMovie.MinimumAvailability = movie.MinimumAvailability; newMovie.MinimumAvailability = movie.MinimumAvailability;
return newMovie; return newMovie;
}
catch (Exception ex)
{
_logger.Warn(ex, "Couldn't map movie {0} to a movie on The Movie DB. It will not be added :(", movie.Title);
return null;
}
} }
} }
} }

View File

@@ -81,7 +81,19 @@ namespace NzbDrone.Core.NetImport
_logger.Debug("Found {0} movies from list(s) {1}", movies.Count, string.Join(", ", lists.Select(l => l.Definition.Name))); _logger.Debug("Found {0} movies from list(s) {1}", movies.Count, string.Join(", ", lists.Select(l => l.Definition.Name)));
return movies; return movies.DistinctBy(x => {
if (x.TmdbId != 0)
{
return x.TmdbId.ToString();
}
if (x.ImdbId.IsNotNullOrWhiteSpace())
{
return x.ImdbId;
}
return x.Title;
}).ToList();
} }
@@ -114,29 +126,29 @@ namespace NzbDrone.Core.NetImport
// listedMovies = listedMovies.Where(ah => importExclusions.Any(h => ah.TmdbId.ToString() != h)).ToList(); // listedMovies = listedMovies.Where(ah => importExclusions.Any(h => ah.TmdbId.ToString() != h)).ToList();
} }
var downloadedCount = 0; //var downloadedCount = 0;
foreach (var movie in listedMovies) foreach (var movie in listedMovies)
{ {
var mapped = _movieSearch.MapMovieToTmdbMovie(movie); var mapped = _movieSearch.MapMovieToTmdbMovie(movie);
if (mapped != null && !importExclusions.Any(x => x == mapped.TmdbId.ToString())) if (mapped != null && !importExclusions.Any(x => x == mapped.TmdbId.ToString()))
{ {
List<DownloadDecision> decisions; //List<DownloadDecision> decisions;
mapped.AddOptions = new AddMovieOptions {SearchForMovie = true}; mapped.AddOptions = new AddMovieOptions {SearchForMovie = true};
_movieService.AddMovie(mapped); _movieService.AddMovie(mapped);
// Search for movie //// Search for movie
try //try
{ //{
decisions = _nzbSearchService.MovieSearch(mapped.Id, false); // decisions = _nzbSearchService.MovieSearch(mapped.Id, false);
} //}
catch (Exception ex) //catch (Exception ex)
{ //{
_logger.Error(ex, $"Unable to search in list for movie {mapped.Id}"); // _logger.Error(ex, $"Unable to search in list for movie {mapped.Id}");
continue; // continue;
} //}
var processed = _processDownloadDecisions.ProcessDecisions(decisions); //var processed = _processDownloadDecisions.ProcessDecisions(decisions);
downloadedCount += processed.Grabbed.Count; //downloadedCount += processed.Grabbed.Count;
} }
else else
{ {
@@ -147,7 +159,7 @@ namespace NzbDrone.Core.NetImport
} }
} }
_logger.ProgressInfo("Movie search completed. {0} reports downloaded.", downloadedCount); //_logger.ProgressInfo("Movie search completed. {0} reports downloaded.", downloadedCount);
} }
private void CleanLibrary(List<Movie> movies) private void CleanLibrary(List<Movie> movies)

View File

@@ -42,7 +42,7 @@ namespace NzbDrone.Core.NetImport.StevenLu
movies.AddIfNotNull(new Tv.Movie() movies.AddIfNotNull(new Tv.Movie()
{ {
Title = item.title, Title = item.title,
ImdbId = item.imdb_id ImdbId = item.imdb_id,
}); });
} }

View File

@@ -89,7 +89,7 @@ namespace NzbDrone.Core.NetImport.Trakt
{ {
var link = Settings.Link.Trim(); var link = Settings.Link.Trim();
var filtersAndLimit = $"?years={Settings.Years}&genres={Settings.Genres.ToLower()}&ratings={Settings.Rating}&certifications={Settings.Ceritification.ToLower()}&limit={Settings.Limit}"; var filtersAndLimit = $"?years={Settings.Years}&genres={Settings.Genres.ToLower()}&ratings={Settings.Rating}&certifications={Settings.Ceritification.ToLower()}&limit={Settings.Limit}{Settings.TraktAdditionalParameters}";
switch (Settings.ListType) switch (Settings.ListType)
{ {
@@ -129,7 +129,7 @@ namespace NzbDrone.Core.NetImport.Trakt
break; break;
} }
Authenticate(); Authenticate();
var request = new NetImportRequest($"{link}", HttpAccept.Json); var request = new NetImportRequest($"{link}", HttpAccept.Json);
request.HttpRequest.Headers.Add("trakt-api-version", "2"); request.HttpRequest.Headers.Add("trakt-api-version", "2");

View File

@@ -96,6 +96,9 @@ namespace NzbDrone.Core.NetImport.Trakt
[FieldDefinition(8, Label = "Limit", HelpText = "Limit the number of movies to get")] [FieldDefinition(8, Label = "Limit", HelpText = "Limit the number of movies to get")]
public int Limit { get; set; } public int Limit { get; set; }
[FieldDefinition(9, Label = "Additional Parameters", HelpText = "Additional Trakt API parameters", Advanced = true)]
public string TraktAdditionalParameters { get; set; }
public NzbDroneValidationResult Validate() public NzbDroneValidationResult Validate()
{ {

View File

@@ -22,7 +22,7 @@ namespace NzbDrone.Core.Notifications.Email
public EmailSettings() public EmailSettings()
{ {
Server = "smtp.google.com"; Server = "smtp.gmail.com";
Port = 587; Port = 587;
Ssl = true; Ssl = true;
} }

View File

@@ -408,8 +408,9 @@
<Compile Include="DecisionEngine\Specifications\UpgradeDiskSpecification.cs" /> <Compile Include="DecisionEngine\Specifications\UpgradeDiskSpecification.cs" />
<Compile Include="DiskSpace\DiskSpace.cs" /> <Compile Include="DiskSpace\DiskSpace.cs" />
<Compile Include="DiskSpace\DiskSpaceService.cs" /> <Compile Include="DiskSpace\DiskSpaceService.cs" />
<Compile Include="Download\Clients\DownloadStation\Proxies\DownloadStationInfoProxy.cs" />
<Compile Include="Download\Clients\DownloadStation\TorrentDownloadStation.cs" /> <Compile Include="Download\Clients\DownloadStation\TorrentDownloadStation.cs" />
<Compile Include="Download\Clients\DownloadStation\Proxies\DownloadStationProxy.cs" /> <Compile Include="Download\Clients\DownloadStation\Proxies\DownloadStationTaskProxy.cs" />
<Compile Include="Download\Clients\DownloadStation\DownloadStationSettings.cs" /> <Compile Include="Download\Clients\DownloadStation\DownloadStationSettings.cs" />
<Compile Include="Download\Clients\DownloadStation\DownloadStationTask.cs" /> <Compile Include="Download\Clients\DownloadStation\DownloadStationTask.cs" />
<Compile Include="Download\Clients\DownloadStation\DownloadStationTaskAdditional.cs" /> <Compile Include="Download\Clients\DownloadStation\DownloadStationTaskAdditional.cs" />
@@ -505,6 +506,7 @@
<Compile Include="Download\Clients\Sabnzbd\JsonConverters\SabnzbdPriorityTypeConverter.cs" /> <Compile Include="Download\Clients\Sabnzbd\JsonConverters\SabnzbdPriorityTypeConverter.cs" />
<Compile Include="Download\Clients\Sabnzbd\JsonConverters\SabnzbdStringArrayConverter.cs" /> <Compile Include="Download\Clients\Sabnzbd\JsonConverters\SabnzbdStringArrayConverter.cs" />
<Compile Include="Download\Clients\Sabnzbd\JsonConverters\SabnzbdQueueTimeConverter.cs" /> <Compile Include="Download\Clients\Sabnzbd\JsonConverters\SabnzbdQueueTimeConverter.cs" />
<Compile Include="Download\Clients\Sabnzbd\Responses\SabnzbdFullStatusResponse.cs" />
<Compile Include="Download\Clients\Sabnzbd\Responses\SabnzbdRetryResponse.cs" /> <Compile Include="Download\Clients\Sabnzbd\Responses\SabnzbdRetryResponse.cs" />
<Compile Include="Download\Clients\Sabnzbd\Responses\SabnzbdAddResponse.cs" /> <Compile Include="Download\Clients\Sabnzbd\Responses\SabnzbdAddResponse.cs" />
<Compile Include="Download\Clients\Sabnzbd\Responses\SabnzbdCategoryResponse.cs" /> <Compile Include="Download\Clients\Sabnzbd\Responses\SabnzbdCategoryResponse.cs" />
@@ -513,6 +515,7 @@
<Compile Include="Download\Clients\Sabnzbd\Sabnzbd.cs" /> <Compile Include="Download\Clients\Sabnzbd\Sabnzbd.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabnzbdCategory.cs" /> <Compile Include="Download\Clients\Sabnzbd\SabnzbdCategory.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabnzbdDownloadStatus.cs" /> <Compile Include="Download\Clients\Sabnzbd\SabnzbdDownloadStatus.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabnzbdFullStatus.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabnzbdHistory.cs" /> <Compile Include="Download\Clients\Sabnzbd\SabnzbdHistory.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabnzbdHistoryItem.cs" /> <Compile Include="Download\Clients\Sabnzbd\SabnzbdHistoryItem.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabnzbdJsonError.cs" /> <Compile Include="Download\Clients\Sabnzbd\SabnzbdJsonError.cs" />
@@ -991,6 +994,12 @@
<Compile Include="Parser\Model\LocalMovie.cs" /> <Compile Include="Parser\Model\LocalMovie.cs" />
<Compile Include="Parser\Model\ParsedMovieInfo.cs" /> <Compile Include="Parser\Model\ParsedMovieInfo.cs" />
<Compile Include="Parser\Model\RemoteMovie.cs" /> <Compile Include="Parser\Model\RemoteMovie.cs" />
<Compile Include="Parser\RomanNumerals\ArabicRomanNumeral.cs" />
<Compile Include="Parser\RomanNumerals\IRomanNumeral.cs" />
<Compile Include="Parser\RomanNumerals\RomanNumeral.cs" />
<Compile Include="Parser\RomanNumerals\RomanNumeralParser.cs" />
<Compile Include="Parser\RomanNumerals\SimpleArabicNumeral.cs" />
<Compile Include="Parser\RomanNumerals\SimpleRomanNumeral.cs" />
<Compile Include="Profiles\Delay\DelayProfile.cs" /> <Compile Include="Profiles\Delay\DelayProfile.cs" />
<Compile Include="Profiles\Delay\DelayProfileService.cs" /> <Compile Include="Profiles\Delay\DelayProfileService.cs" />
<Compile Include="Profiles\Delay\DelayProfileTagInUseValidator.cs" /> <Compile Include="Profiles\Delay\DelayProfileTagInUseValidator.cs" />
@@ -1266,6 +1275,7 @@
<Compile Include="Datastore\Migration\131_make_parsed_episode_info_nullable.cs" /> <Compile Include="Datastore\Migration\131_make_parsed_episode_info_nullable.cs" />
<Compile Include="Housekeeping\Housekeepers\FixWronglyMatchedMovieFiles.cs" /> <Compile Include="Housekeeping\Housekeepers\FixWronglyMatchedMovieFiles.cs" />
<Compile Include="Datastore\Migration\135_add_haspredbentry_to_movies.cs" /> <Compile Include="Datastore\Migration\135_add_haspredbentry_to_movies.cs" />
<Compile Include="Tv\QueryExtensions.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.0,Profile=Client"> <BootstrapperPackage Include=".NETFramework,Version=v4.0,Profile=Client">

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Text; using System.Text;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
@@ -26,6 +26,8 @@ namespace NzbDrone.Core.Parser.Model
public string Codec { get; set; } public string Codec { get; set; }
public string Resolution { get; set; } public string Resolution { get; set; }
public IndexerFlags IndexerFlags { get; set; }
public int Age public int Age
{ {
get get
@@ -91,4 +93,15 @@ namespace NzbDrone.Core.Parser.Model
} }
} }
} }
[Flags]
public enum IndexerFlags
{
G_Freeleech = 1, //General
G_Halfleech = 2, //General, only 1/2 of download counted
G_DoubleUpload = 4, //General
PTP_Golden = 8, //PTP
PTP_Approved = 16, //PTP
HDB_Internal = 32 //HDBits
}
} }

View File

@@ -273,7 +273,7 @@ namespace NzbDrone.Core.Parser
private static readonly Regex ReportImdbId = new Regex(@"(?<imdbid>tt\d{7})", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex ReportImdbId = new Regex(@"(?<imdbid>tt\d{7})", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex SimpleTitleRegex = new Regex(@"(?:480[ip]|576[ip]|720[ip]|1080[ip]|2160[ip]|[xh][\W_]?26[45]|DD\W?5\W1|[<>?*:|]|848x480|1280x720|1920x1080|(8|10)b(it)?)\s*", private static readonly Regex SimpleTitleRegex = new Regex(@"\s*(?:480[ip]|576[ip]|720[ip]|1080[ip]|2160[ip]|[xh][\W_]?26[45]|DD\W?5\W1|[<>?*:|]|848x480|1280x720|1920x1080|(8|10)b(it)?)",
RegexOptions.IgnoreCase | RegexOptions.Compiled); RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex WebsitePrefixRegex = new Regex(@"^\[\s*[a-z]+(\.[a-z]+)+\s*\][- ]*", private static readonly Regex WebsitePrefixRegex = new Regex(@"^\[\s*[a-z]+(\.[a-z]+)+\s*\][- ]*",
@@ -310,6 +310,12 @@ namespace NzbDrone.Core.Parser
private static readonly Regex RequestInfoRegex = new Regex(@"\[.+?\]", RegexOptions.Compiled); private static readonly Regex RequestInfoRegex = new Regex(@"\[.+?\]", RegexOptions.Compiled);
private static readonly string[] Numbers = new[] { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" }; private static readonly string[] Numbers = new[] { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };
private static Dictionary<String, String> _umlautMappings = new Dictionary<string, string>
{
{"ö", "oe"},
{"ä", "ae"},
{"ü", "ue"},
};
public static ParsedEpisodeInfo ParsePath(string path) public static ParsedEpisodeInfo ParsePath(string path)
{ {
@@ -405,8 +411,13 @@ namespace NzbDrone.Core.Parser
if (result != null) if (result != null)
{ {
var languageTitle = simpleTitle;
if (result.MovieTitle.IsNotNullOrWhiteSpace() )
{
languageTitle = simpleTitle.Replace(result.MovieTitle, "A Movie");
}
result.Language = LanguageParser.ParseLanguage(simpleTitle.Replace(result.MovieTitle, "A Movie")); result.Language = LanguageParser.ParseLanguage(languageTitle);
Logger.Debug("Language parsed: {0}", result.Language); Logger.Debug("Language parsed: {0}", result.Language);
result.Quality = QualityParser.ParseQuality(title); result.Quality = QualityParser.ParseQuality(title);
@@ -655,7 +666,7 @@ namespace NzbDrone.Core.Parser
if (long.TryParse(title, out number)) if (long.TryParse(title, out number))
return title; return title;
return NormalizeRegex.Replace(title, string.Empty).ToLower().RemoveAccent(); return ReplaceGermanUmlauts(NormalizeRegex.Replace(title, string.Empty).ToLower()).RemoveAccent();
} }
public static string NormalizeEpisodeTitle(string title) public static string NormalizeEpisodeTitle(string title)

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using NLog; using NLog;
@@ -7,6 +8,7 @@ using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Parser.RomanNumerals;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Parser namespace NzbDrone.Core.Parser
@@ -33,20 +35,8 @@ namespace NzbDrone.Core.Parser
private readonly ISceneMappingService _sceneMappingService; private readonly ISceneMappingService _sceneMappingService;
private readonly IMovieService _movieService; private readonly IMovieService _movieService;
private readonly Logger _logger; private readonly Logger _logger;
private readonly Dictionary<string, string> romanNumeralsMapper = new Dictionary<string, string> private static HashSet<ArabicRomanNumeral> _arabicRomanNumeralMappings;
{
{ "1", "I"},
{ "2", "II"},
{ "3", "III"},
{ "4", "IV"},
{ "5", "V"},
{ "6", "VI"},
{ "7", "VII"},
{ "8", "VII"},
{ "9", "IX"},
{ "10", "X"},
}; //If a movie has more than 10 parts fuck 'em.
public ParsingService(IEpisodeService episodeService, public ParsingService(IEpisodeService episodeService,
ISeriesService seriesService, ISeriesService seriesService,
@@ -59,6 +49,11 @@ namespace NzbDrone.Core.Parser
_sceneMappingService = sceneMappingService; _sceneMappingService = sceneMappingService;
_movieService = movieService; _movieService = movieService;
_logger = logger; _logger = logger;
if (_arabicRomanNumeralMappings == null)
{
_arabicRomanNumeralMappings = RomanNumeralParser.GetArabicRomanNumeralsMapping();
}
} }
public LocalEpisode GetLocalEpisode(string filename, Series series) public LocalEpisode GetLocalEpisode(string filename, Series series)
@@ -184,7 +179,7 @@ namespace NzbDrone.Core.Parser
return _movieService.FindByTitle(title); return _movieService.FindByTitle(title);
} }
var movies = _movieService.FindByTitle(parsedMovieInfo.MovieTitle); var movies = _movieService.FindByTitle(parsedMovieInfo.MovieTitle, parsedMovieInfo.Year);
if (movies == null) if (movies == null)
{ {
@@ -354,85 +349,117 @@ namespace NzbDrone.Core.Parser
private Movie GetMovie(ParsedMovieInfo parsedMovieInfo, string imdbId, SearchCriteriaBase searchCriteria) private Movie GetMovie(ParsedMovieInfo parsedMovieInfo, string imdbId, SearchCriteriaBase searchCriteria)
{ {
// TODO: Answer me this: Wouldn't it be smarter to start out looking for a movie if we have an ImDb Id?
if (!String.IsNullOrWhiteSpace(imdbId))
{
Movie movieByImDb;
if (TryGetMovieByImDbId(parsedMovieInfo, imdbId, out movieByImDb))
{
return movieByImDb;
}
}
if (searchCriteria != null) if (searchCriteria != null)
{ {
var possibleTitles = new List<string>(); Movie movieBySearchCriteria;
if (TryGetMovieBySearchCriteria(parsedMovieInfo, searchCriteria, out movieBySearchCriteria))
Movie possibleMovie = null;
possibleTitles.Add(searchCriteria.Movie.CleanTitle);
foreach (string altTitle in searchCriteria.Movie.AlternativeTitles)
{ {
possibleTitles.Add(altTitle.CleanSeriesTitle()); return movieBySearchCriteria;
}
}
else
{
Movie movieByTitleAndOrYear;
if (TryGetMovieByTitleAndOrYear(parsedMovieInfo, out movieByTitleAndOrYear))
{
return movieByTitleAndOrYear;
}
}
// nothing found up to here => logging that and returning null
_logger.Debug($"No matching movie {parsedMovieInfo.MovieTitle}");
return null;
}
private bool TryGetMovieByImDbId(ParsedMovieInfo parsedMovieInfo, string imdbId, out Movie movie)
{
movie = _movieService.FindByImdbId(imdbId);
//Should fix practically all problems, where indexer is shite at adding correct imdbids to movies.
if (movie != null && parsedMovieInfo.Year > 1800 && parsedMovieInfo.Year != movie.Year)
{
movie = null;
return false;
}
return movie != null;
}
private bool TryGetMovieByTitleAndOrYear(ParsedMovieInfo parsedMovieInfo, out Movie movieByTitleAndOrYear)
{
Func<Movie, bool> isNotNull = movie => movie != null;
if (parsedMovieInfo.Year > 1800)
{
movieByTitleAndOrYear = _movieService.FindByTitle(parsedMovieInfo.MovieTitle, parsedMovieInfo.Year);
if (isNotNull(movieByTitleAndOrYear))
{
return true;
}
}
movieByTitleAndOrYear = _movieService.FindByTitle(parsedMovieInfo.MovieTitle);
if (isNotNull(movieByTitleAndOrYear))
{
return true;
}
movieByTitleAndOrYear = null;
return false;
}
private bool TryGetMovieBySearchCriteria(ParsedMovieInfo parsedMovieInfo, SearchCriteriaBase searchCriteria, out Movie possibleMovie)
{
possibleMovie = null;
List<string> possibleTitles = new List<string>();
possibleTitles.Add(searchCriteria.Movie.CleanTitle);
foreach (string altTitle in searchCriteria.Movie.AlternativeTitles)
{
possibleTitles.Add(altTitle.CleanSeriesTitle());
}
string cleanTitle = parsedMovieInfo.MovieTitle.CleanSeriesTitle();
foreach (string title in possibleTitles)
{
if (title == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
{
possibleMovie = searchCriteria.Movie;
} }
foreach (string title in possibleTitles) foreach (ArabicRomanNumeral numeralMapping in _arabicRomanNumeralMappings)
{ {
if (title == parsedMovieInfo.MovieTitle.CleanSeriesTitle()) string arabicNumeral = numeralMapping.ArabicNumeralAsString;
string romanNumeral = numeralMapping.RomanNumeralLowerCase;
_logger.Debug(cleanTitle);
if (title.Replace(arabicNumeral, romanNumeral) == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
{ {
possibleMovie = searchCriteria.Movie; possibleMovie = searchCriteria.Movie;
} }
foreach (KeyValuePair<string, string> entry in romanNumeralsMapper) if (title == parsedMovieInfo.MovieTitle.CleanSeriesTitle().Replace(arabicNumeral, romanNumeral))
{ {
string num = entry.Key; possibleMovie = searchCriteria.Movie;
string roman = entry.Value.ToLower();
if (title.Replace(num, roman) == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
{
possibleMovie = searchCriteria.Movie;
}
if (title.Replace(roman, num) == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
{
possibleMovie = searchCriteria.Movie;
}
} }
} }
if (possibleMovie != null && (parsedMovieInfo.Year < 1800 || possibleMovie.Year == parsedMovieInfo.Year))
{
return possibleMovie;
}
} }
Movie movie = null; if (possibleMovie != null && (parsedMovieInfo.Year < 1800 || possibleMovie.Year == parsedMovieInfo.Year))
if (searchCriteria == null)
{ {
if (parsedMovieInfo.Year > 1900) return true;
{
movie = _movieService.FindByTitle(parsedMovieInfo.MovieTitle, parsedMovieInfo.Year);
}
else
{
movie = _movieService.FindByTitle(parsedMovieInfo.MovieTitle);
}
if (movie == null)
{
movie = _movieService.FindByTitle(parsedMovieInfo.MovieTitle);
}
// return movie;
} }
possibleMovie = null;
return false;
if (movie == null && imdbId.IsNotNullOrWhiteSpace())
{
movie = _movieService.FindByImdbId(imdbId);
}
if (movie == null)
{
_logger.Debug($"No matching movie {parsedMovieInfo.MovieTitle}");
return null;
}
return movie;
} }
private Series GetSeries(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria) private Series GetSeries(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria)

View File

@@ -0,0 +1,18 @@
namespace NzbDrone.Core.Parser.RomanNumerals
{
public class ArabicRomanNumeral
{
public ArabicRomanNumeral(int arabicNumeral, string arabicNumeralAsString, string romanNumeral)
{
ArabicNumeral = arabicNumeral;
ArabicNumeralAsString = arabicNumeralAsString;
RomanNumeral = romanNumeral;
}
public int ArabicNumeral { get; private set; }
public string ArabicNumeralAsString { get; private set; }
public string RomanNumeral { get; private set; }
public string RomanNumeralLowerCase => RomanNumeral.ToLower();
}
}

View File

@@ -0,0 +1,12 @@
namespace NzbDrone.Core.Parser.RomanNumerals
{
public interface IRomanNumeral
{
int CompareTo(object obj);
int CompareTo(RomanNumeral other);
bool Equals(RomanNumeral other);
int ToInt();
long ToLong();
string ToString();
}
}

View File

@@ -0,0 +1,357 @@
using System;
using System.Text;
namespace NzbDrone.Core.Parser.RomanNumerals
{
/// <summary>
/// Represents the numeric system used in ancient Rome, employing combinations of letters from the Latin alphabet to signify values.
/// Implementation adapted from: http://www.c-sharpcorner.com/Blogs/14255/converting-to-and-from-roman-numerals.aspx
/// </summary>
public class RomanNumeral : IComparable, IComparable<RomanNumeral>, IEquatable<RomanNumeral>, IRomanNumeral
{
#region Fields
/// <summary>
/// The numeric value of the roman numeral.
/// </summary>
private readonly int _value;
/// <summary>
/// Represents the smallest possible value of an <see cref="T:RomanNumeral"/>. This field is constant.
/// </summary>
public static readonly int MinValue = 1;
/// <summary>
/// Represents the largest possible value of an <see cref="T:RomanNumeral"/>. This field is constant.
/// </summary>
public static readonly int MaxValue = 3999;
private static readonly string[] Thousands = { "MMM", "MM", "M" };
private static readonly string[] Hundreds = { "CM", "DCCC", "DCC", "DC", "D", "CD", "CCC", "CC", "C" };
private static readonly string[] Tens = { "XC", "LXXX", "LXX", "LX", "L", "XL", "XXX", "XX", "X" };
private static readonly string[] Units = { "IX", "VIII", "VII", "VI", "V", "IV", "III", "II", "I" };
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="RomanNumeral"/> class.
/// </summary>
public RomanNumeral()
{
_value = 1;
}
/// <summary>
/// Initializes a new instance of the <see cref="RomanNumeral"/> class.
/// </summary>
/// <param name="value">The value.</param>
public RomanNumeral(int value)
{
_value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="RomanNumeral"/> class.
/// </summary>
/// <param name="romanNumeral">The roman numeral.</param>
public RomanNumeral(string romanNumeral)
{
int value;
if (TryParse(romanNumeral, out value))
{
_value = value;
}
}
#endregion
#region Methods
/// <summary>
/// Converts this instance to an integer.
/// </summary>
/// <returns>A numeric int representation.</returns>
public int ToInt()
{
return _value;
}
/// <summary>
/// Converts this instance to a long.
/// </summary>
/// <returns>A numeric long representation.</returns>
public long ToLong()
{
return _value;
}
/// <summary>
/// Converts the string representation of a number to its 32-bit signed integer equivalent. A return value indicates whether the conversion succeeded.
/// </summary>
/// <param name="text">A string containing a number to convert. </param>
/// <param name="value">When this method returns, contains the 32-bit signed integer value equivalent of the number contained in <paramref name="text"/>,
/// if the conversion succeeded, or zero if the conversion failed. The conversion fails if the <paramref name="text"/> parameter is null or
/// <see cref="F:System.String.Empty"/>, is not of the correct format, or represents a number less than <see cref="F:System.Int32.MinValue"/> or greater than <see cref="F:System.Int32.MaxValue"/>. This parameter is passed uninitialized. </param><filterpriority>1</filterpriority>
/// <returns>
/// true if <paramref name="text"/> was converted successfully; otherwise, false.
/// </returns>
public static bool TryParse(string text, out int value)
{
value = 0;
if (string.IsNullOrEmpty(text)) return false;
text = text.ToUpper();
int len = 0;
for (int i = 0; i < 3; i++)
{
if (text.StartsWith(Thousands[i]))
{
value += 1000 * (3 - i);
len = Thousands[i].Length;
break;
}
}
if (len > 0)
{
text = text.Substring(len);
len = 0;
}
for (int i = 0; i < 9; i++)
{
if (text.StartsWith(Hundreds[i]))
{
value += 100 * (9 - i);
len = Hundreds[i].Length;
break;
}
}
if (len > 0)
{
text = text.Substring(len);
len = 0;
}
for (int i = 0; i < 9; i++)
{
if (text.StartsWith(Tens[i]))
{
value += 10 * (9 - i);
len = Tens[i].Length;
break;
}
}
if (len > 0)
{
text = text.Substring(len);
len = 0;
}
for (int i = 0; i < 9; i++)
{
if (text.StartsWith(Units[i]))
{
value += 9 - i;
len = Units[i].Length;
break;
}
}
if (text.Length > len)
{
value = 0;
return false;
}
return true;
}
/// <summary>
/// Converts a number into a roman numeral.
/// </summary>
/// <param name="number">The number.</param>
/// <returns></returns>
private static string ToRomanNumeral(int number)
{
RangeGuard(number);
int thousands, hundreds, tens, units;
thousands = number / 1000;
number %= 1000;
hundreds = number / 100;
number %= 100;
tens = number / 10;
units = number % 10;
var sb = new StringBuilder();
if (thousands > 0) sb.Append(Thousands[3 - thousands]);
if (hundreds > 0) sb.Append(Hundreds[9 - hundreds]);
if (tens > 0) sb.Append(Tens[9 - tens]);
if (units > 0) sb.Append(Units[9 - units]);
return sb.ToString();
}
/// <summary>
/// Returns the Roman numeral that was passed in as either an Arabic numeral
/// or a Roman numeral.
/// </summary>
/// <returns>A <see cref="System.String" /> representing a Roman Numeral</returns>
public string ToRomanNumeral()
{
return ToString();
}
/// <summary>
/// Determines whether a given number is within the valid range of values for a roman numeral.
/// </summary>
/// <param name="number">The number to validate.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// $Roman numerals can not be larger than {MaxValue}.
/// or
/// $Roman numerals can not be smaller than {MinValue}.
/// </exception>
private static void RangeGuard(int number)
{
if (number > MaxValue)
throw new ArgumentOutOfRangeException(nameof(number), number,
$"Roman numerals can not be larger than {MaxValue}.");
if (number < MinValue)
throw new ArgumentOutOfRangeException(nameof(number), number,
$"Roman numerals can not be smaller than {MinValue}.");
}
#endregion
#region Operators
/// <summary>
/// Implements the operator *.
/// </summary>
/// <param name="firstNumeral">The first numeral.</param>
/// <param name="secondNumeral">The second numeral.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static RomanNumeral operator *(RomanNumeral firstNumeral, RomanNumeral secondNumeral)
{
return new RomanNumeral(firstNumeral._value * secondNumeral._value);
}
/// <summary>
/// Implements the operator /.
/// </summary>
/// <param name="numerator">The numerator.</param>
/// <param name="denominator">The denominator.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static RomanNumeral operator /(RomanNumeral numerator, RomanNumeral denominator)
{
return new RomanNumeral(numerator._value / denominator._value);
}
/// <summary>
/// Implements the operator +.
/// </summary>
/// <param name="firstNumeral">The first numeral.</param>
/// <param name="secondNumeral">The second numeral.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static RomanNumeral operator +(RomanNumeral firstNumeral, RomanNumeral secondNumeral)
{
return new RomanNumeral(firstNumeral._value + secondNumeral._value);
}
/// <summary>
/// Implements the operator -.
/// </summary>
/// <param name="firstNumeral">The first numeral.</param>
/// <param name="secondNumeral">The second numeral.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static RomanNumeral operator -(RomanNumeral firstNumeral, RomanNumeral secondNumeral)
{
return new RomanNumeral(firstNumeral._value - secondNumeral._value);
}
#endregion
#region Interface Implementations
/// <summary>
/// </summary>
/// <param name="obj">The object.</param>
/// <returns></returns>
public int CompareTo(object obj)
{
if (obj is sbyte
|| obj is byte
|| obj is short
|| obj is ushort
|| obj is int
|| obj is uint
|| obj is long
|| obj is ulong
|| obj is float
|| obj is double
|| obj is decimal)
{
var value = (int)obj;
return _value.CompareTo(value);
}
else if (obj is string)
{
int value;
var numeral = obj as string;
if (TryParse(numeral, out value))
{
return _value.CompareTo(value);
}
}
return 0;
}
/// <summary>
/// Compares to.
/// </summary>
/// <param name="other">The other.</param>
/// <returns></returns>
public int CompareTo(RomanNumeral other)
{
return _value.CompareTo(other._value);
}
/// <summary>
/// Equalses the specified other.
/// </summary>
/// <param name="other">The other.</param>
/// <returns></returns>
public bool Equals(RomanNumeral other)
{
return _value == other._value;
}
/// <summary>
/// Returns the Roman Numeral which was passed to this Instance
/// during creation.
/// </summary>
/// <returns>
/// A <see cref="System.String" /> that represents a Roman Numeral.
/// </returns>
public override string ToString()
{
return ToRomanNumeral(_value);
}
#endregion
}
}

View File

@@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Parser.RomanNumerals
{
public static class RomanNumeralParser
{
private const int DICTIONARY_PREPOPULATION_SIZE = 20;
private static HashSet<ArabicRomanNumeral> _arabicRomanNumeralsMapping;
private static Dictionary<SimpleArabicNumeral, SimpleRomanNumeral> _simpleArabicNumeralMappings;
static RomanNumeralParser()
{
PopluateDictionariesReasonablyLarge();
}
private static void PopluateDictionariesReasonablyLarge()
{
if(_simpleArabicNumeralMappings != null || _arabicRomanNumeralsMapping != null)
{
return;
}
_arabicRomanNumeralsMapping = new HashSet<ArabicRomanNumeral>();
_simpleArabicNumeralMappings = new Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>();
foreach (int arabicNumeral in Enumerable.Range(1,DICTIONARY_PREPOPULATION_SIZE +1))
{
string romanNumeralAsString, arabicNumeralAsString;
GenerateRomanNumerals(arabicNumeral, out romanNumeralAsString, out arabicNumeralAsString);
ArabicRomanNumeral arm = new ArabicRomanNumeral(arabicNumeral, arabicNumeralAsString, romanNumeralAsString);
_arabicRomanNumeralsMapping.Add(arm);
SimpleArabicNumeral sam = new SimpleArabicNumeral(arabicNumeral);
SimpleRomanNumeral srm = new SimpleRomanNumeral(romanNumeralAsString);
_simpleArabicNumeralMappings.Add(sam, srm);
}
}
private static void GenerateRomanNumerals(int arabicNumeral, out string romanNumeral, out string arabicNumeralAsString)
{
RomanNumeral romanNumeralObject = new RomanNumeral(arabicNumeral);
romanNumeral = romanNumeralObject.ToRomanNumeral();
arabicNumeralAsString = Convert.ToString(arabicNumeral);
}
private static HashSet<ArabicRomanNumeral> GenerateAdditionalMappings(int offset, int length)
{
HashSet<ArabicRomanNumeral> additionalArabicRomanNumerals = new HashSet<ArabicRomanNumeral>();
foreach (int arabicNumeral in Enumerable.Range(offset, length))
{
string romanNumeral;
string arabicNumeralAsString;
GenerateRomanNumerals(arabicNumeral, out romanNumeral, out arabicNumeralAsString);
ArabicRomanNumeral arm = new ArabicRomanNumeral(arabicNumeral, arabicNumeralAsString, romanNumeral);
additionalArabicRomanNumerals.Add(arm);
}
return additionalArabicRomanNumerals;
}
public static HashSet<ArabicRomanNumeral> GetArabicRomanNumeralsMapping(int upToArabicNumber = DICTIONARY_PREPOPULATION_SIZE)
{
if (upToArabicNumber == DICTIONARY_PREPOPULATION_SIZE)
{
return new HashSet<ArabicRomanNumeral>(_arabicRomanNumeralsMapping.Take(upToArabicNumber));
}
if (upToArabicNumber < DICTIONARY_PREPOPULATION_SIZE)
{
return
(HashSet<ArabicRomanNumeral>)
new HashSet<ArabicRomanNumeral>(_arabicRomanNumeralsMapping).Take(upToArabicNumber);
}
if (upToArabicNumber >= DICTIONARY_PREPOPULATION_SIZE)
{
if (_arabicRomanNumeralsMapping.Count >= upToArabicNumber)
{
return new HashSet<ArabicRomanNumeral>(_arabicRomanNumeralsMapping.Take(upToArabicNumber));
}
HashSet<ArabicRomanNumeral> largerMapping = GenerateAdditionalMappings(DICTIONARY_PREPOPULATION_SIZE + 1, upToArabicNumber);
_arabicRomanNumeralsMapping = (HashSet<ArabicRomanNumeral>)_arabicRomanNumeralsMapping.Union(largerMapping);
}
return _arabicRomanNumeralsMapping;
}
public static Dictionary<SimpleArabicNumeral, SimpleRomanNumeral> GetArabicRomanNumeralAsDictionary(
int upToArabicNumer = DICTIONARY_PREPOPULATION_SIZE)
{
Func
<Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>, int,
Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>> take =
(mapping, amountToTake) =>
new Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>(
mapping.Take(amountToTake).ToDictionary(key => key.Key, value => value.Value));
if (upToArabicNumer == DICTIONARY_PREPOPULATION_SIZE)
{
return take(_simpleArabicNumeralMappings, upToArabicNumer);
}
if (upToArabicNumer > DICTIONARY_PREPOPULATION_SIZE)
{
if (_simpleArabicNumeralMappings.Count >= upToArabicNumer)
{
return take(_simpleArabicNumeralMappings, upToArabicNumer);
}
var moreSimpleNumerals = GenerateAdditionalSimpleNumerals(DICTIONARY_PREPOPULATION_SIZE, upToArabicNumer);
_simpleArabicNumeralMappings =
(Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>)
_simpleArabicNumeralMappings.Union(moreSimpleNumerals);
return take(_simpleArabicNumeralMappings, _arabicRomanNumeralsMapping.Count);
}
if (upToArabicNumer < DICTIONARY_PREPOPULATION_SIZE)
{
return take(_simpleArabicNumeralMappings, upToArabicNumer);
}
return _simpleArabicNumeralMappings;
}
private static Dictionary<SimpleArabicNumeral, SimpleRomanNumeral> GenerateAdditionalSimpleNumerals(int offset,
int length)
{
Dictionary<SimpleArabicNumeral,SimpleRomanNumeral> moreNumerals = new Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>();
foreach (int arabicNumeral in Enumerable.Range(offset, length))
{
string romanNumeral;
string arabicNumeralAsString;
GenerateRomanNumerals(arabicNumeral, out romanNumeral, out arabicNumeralAsString);
SimpleArabicNumeral san = new SimpleArabicNumeral(arabicNumeral);
SimpleRomanNumeral srn = new SimpleRomanNumeral(romanNumeral);
moreNumerals.Add(san,srn);
}
return moreNumerals;
}
}
}

View File

@@ -0,0 +1,15 @@
using System;
namespace NzbDrone.Core.Parser.RomanNumerals
{
public class SimpleArabicNumeral
{
public SimpleArabicNumeral(int numeral)
{
Numeral = numeral;
}
public int Numeral { get; private set; }
public string NumeralAsString => Convert.ToString(Numeral);
}
}

View File

@@ -0,0 +1,13 @@
namespace NzbDrone.Core.Parser.RomanNumerals
{
public class SimpleRomanNumeral
{
public SimpleRomanNumeral(string numeral)
{
Numeral = numeral;
}
public string Numeral { get; private set; }
public string NumeralLowerCase => Numeral.ToLower();
}
}

View File

@@ -98,12 +98,18 @@ namespace NzbDrone.Core.Tv
return true; return true;
} }
return DateTime.Now >= MinimumAvailabilityDate.AddDays((double)delay); if (MinimumAvailabilityDate == DateTime.MinValue || MinimumAvailabilityDate == DateTime.MaxValue)
{
return DateTime.Now >= MinimumAvailabilityDate;
}
return DateTime.Now >= MinimumAvailabilityDate.AddDays((double)delay);
} }
public override string ToString() public override string ToString()
{ {
return string.Format("[{0}][{1}]", ImdbId, Title.NullSafe()); return string.Format("[{0}][{1} ({2})]", ImdbId, Title.NullSafe(), Year.NullSafe());
} }
} }

View File

@@ -6,7 +6,9 @@ using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Datastore.Extensions; using NzbDrone.Core.Datastore.Extensions;
using Marr.Data.QGen; using Marr.Data.QGen;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Parser.RomanNumerals;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using CoreParser = NzbDrone.Core.Parser.Parser;
namespace NzbDrone.Core.Tv namespace NzbDrone.Core.Tv
{ {
@@ -28,21 +30,6 @@ namespace NzbDrone.Core.Tv
public class MovieRepository : BasicRepository<Movie>, IMovieRepository public class MovieRepository : BasicRepository<Movie>, IMovieRepository
{ {
private readonly Dictionary<string, string> romanNumeralsMapper = new Dictionary<string, string>
{
{ "1", "I"},
{ "2", "II"},
{ "3", "III"},
{ "4", "IV"},
{ "5", "V"},
{ "6", "VI"},
{ "7", "VII"},
{ "8", "VII"},
{ "9", "IX"},
{ "10", "X"},
}; //If a movie has more than 10 parts fuck 'em.
protected IMainDatabase _database; protected IMainDatabase _database;
public MovieRepository(IMainDatabase database, IEventAggregator eventAggregator) public MovieRepository(IMainDatabase database, IEventAggregator eventAggregator)
@@ -58,94 +45,12 @@ namespace NzbDrone.Core.Tv
public Movie FindByTitle(string cleanTitle) public Movie FindByTitle(string cleanTitle)
{ {
cleanTitle = cleanTitle.ToLowerInvariant(); return FindByTitle(cleanTitle, null);
var cleanRoman = cleanTitle;
var cleanNum = cleanTitle;
foreach (KeyValuePair<string, string> entry in romanNumeralsMapper)
{
string num = entry.Key;
string roman = entry.Value.ToLower();
cleanRoman = cleanRoman.Replace(num, roman);
cleanNum = cleanNum.Replace(roman, num);
}
var result = Query.Where(s => s.CleanTitle == cleanTitle).FirstOrDefault();
if (result == null)
{
result = Query.Where(s => s.CleanTitle == cleanNum).OrWhere(s => s.CleanTitle == cleanRoman).FirstOrDefault();
if (result == null)
{
var movies = this.All();
result = movies.Where(m => m.AlternativeTitles.Any(t => Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanTitle ||
Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanRoman ||
Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanNum)).FirstOrDefault();
return result;
}
else
{
return result;
}
}
else
{
return result;
}
} }
public Movie FindByTitle(string cleanTitle, int year) public Movie FindByTitle(string cleanTitle, int year)
{ {
cleanTitle = cleanTitle.ToLowerInvariant(); return FindByTitle(cleanTitle, year as int?);
var cleanRoman = cleanTitle;
var cleanNum = cleanTitle;
foreach (KeyValuePair<string, string> entry in romanNumeralsMapper)
{
string num = entry.Key;
string roman = entry.Value.ToLower();
cleanRoman = cleanRoman.Replace(num, roman);
cleanNum = cleanNum.Replace(roman, num);
}
var results = Query.Where(s => s.CleanTitle == cleanTitle);
if (results == null)
{
results = Query.Where(s => s.CleanTitle == cleanNum).OrWhere(s => s.CleanTitle == cleanRoman);
if (results == null)
{
var movies = this.All();
var listResults = movies.Where(m => m.AlternativeTitles.Any(t => Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanTitle ||
Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanRoman ||
Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanNum));
return listResults.Where(m => m.Year == year).FirstOrDefault();
}
else
{
return results.Where(m => m.Year == year).FirstOrDefault();
}
}
else
{
return results.Where(m => m.Year == year).FirstOrDefault();
}
} }
public Movie FindByImdbId(string imdbid) public Movie FindByImdbId(string imdbid)
@@ -318,6 +223,48 @@ namespace NzbDrone.Core.Tv
return string.Format("({0})", string.Join(" OR ", clauses)); return string.Format("({0})", string.Join(" OR ", clauses));
} }
private Movie FindByTitle(string cleanTitle, int? year)
{
cleanTitle = cleanTitle.ToLowerInvariant();
string cleanTitleWithRomanNumbers = cleanTitle;
string cleanTitleWithArabicNumbers = cleanTitle;
foreach (ArabicRomanNumeral arabicRomanNumeral in RomanNumeralParser.GetArabicRomanNumeralsMapping())
{
string arabicNumber = arabicRomanNumeral.ArabicNumeralAsString;
string romanNumber = arabicRomanNumeral.RomanNumeral;
cleanTitleWithRomanNumbers = cleanTitleWithRomanNumbers.Replace(arabicNumber, romanNumber);
cleanTitleWithArabicNumbers = cleanTitleWithArabicNumbers.Replace(romanNumber, arabicNumber);
}
Movie result = Query.Where(s => s.CleanTitle == cleanTitle).FirstWithYear(year);
if (result == null)
{
result = Query.Where(movie => movie.CleanTitle == cleanTitleWithArabicNumbers).FirstWithYear(year) ??
Query.Where(movie => movie.CleanTitle == cleanTitleWithRomanNumbers).FirstWithYear(year);
if (result == null)
{
IEnumerable<Movie> movies = All();
Func<string, string> titleCleaner = title => CoreParser.CleanSeriesTitle(title.ToLower());
Func<IEnumerable<string>, string, bool> altTitleComparer =
(alternativeTitles, atitle) =>
alternativeTitles.Any(altTitle => titleCleaner(altTitle) == atitle);
result = movies.Where(m => altTitleComparer(m.AlternativeTitles, cleanTitle) ||
altTitleComparer(m.AlternativeTitles, cleanTitleWithRomanNumbers) ||
altTitleComparer(m.AlternativeTitles, cleanTitleWithArabicNumbers)).FirstWithYear(year);
}
}
return result;
/*return year.HasValue
? results?.FirstOrDefault(movie => movie.Year == year.Value)
: results?.FirstOrDefault();*/
}
public Movie FindByTmdbId(int tmdbid) public Movie FindByTmdbId(int tmdbid)
{ {
return Query.Where(m => m.TmdbId == tmdbid).FirstOrDefault(); return Query.Where(m => m.TmdbId == tmdbid).FirstOrDefault();

View File

@@ -53,6 +53,7 @@ namespace NzbDrone.Core.Tv
private readonly IBuildFileNames _fileNameBuilder; private readonly IBuildFileNames _fileNameBuilder;
private readonly Logger _logger; private readonly Logger _logger;
public MovieService(IMovieRepository movieRepository, public MovieService(IMovieRepository movieRepository,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
ISceneMappingService sceneMappingService, ISceneMappingService sceneMappingService,

View File

@@ -0,0 +1,30 @@
using System;
using System.Linq;
using System.Collections.Generic;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Datastore.Extensions;
using Marr.Data.QGen;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Parser.RomanNumerals;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using CoreParser = NzbDrone.Core.Parser.Parser;
namespace NzbDrone.Core
{
public static class QueryExtensions
{
public static Movie FirstWithYear(this SortBuilder<Movie> query, int? year)
{
return year.HasValue ? query.FirstOrDefault(movie => movie.Year == year) : query.FirstOrDefault();
}
}
public static class EnumerableExtensions
{
public static Movie FirstWithYear(this IEnumerable<Movie> query, int? year)
{
return year.HasValue ? query.FirstOrDefault(movie => movie.Year == year) : query.FirstOrDefault();
}
}
}

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using Mono.Unix; using Mono.Unix;
using Mono.Unix.Native; using Mono.Unix.Native;
using NLog; using NLog;
@@ -17,21 +18,50 @@ namespace NzbDrone.Mono.Disk
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(DiskProvider)); private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(DiskProvider));
private readonly IProcMountProvider _procMountProvider; private readonly IProcMountProvider _procMountProvider;
private readonly NzbDrone.Mono.Disk.ISymbolicLinkResolver _symLinkResolver; private readonly ISymbLinkResolver _symLinkResolver;
private readonly Logger _logger;
// Mono supports sending -1 for a uint to indicate that the owner or group should not be set // Mono supports sending -1 for a uint to indicate that the owner or group should not be set
// `unchecked((uint)-1)` and `uint.MaxValue` are the same thing. // `unchecked((uint)-1)` and `uint.MaxValue` are the same thing.
private const uint UNCHANGED_ID = uint.MaxValue; private const uint UNCHANGED_ID = uint.MaxValue;
public DiskProvider(IProcMountProvider procMountProvider, NzbDrone.Mono.Disk.ISymbolicLinkResolver symLinkResolver) public DiskProvider(IProcMountProvider procMountProvider, ISymbLinkResolver symLinkResolver, Logger logger)
{ {
_procMountProvider = procMountProvider; _procMountProvider = procMountProvider;
_symLinkResolver = symLinkResolver; _symLinkResolver = symLinkResolver;
_logger = logger;
} }
public override IMount GetMount(string path) public override IMount GetMount(string path)
{ {
path = _symLinkResolver.GetCompleteRealPath(path); if (path == null) return null;
try
{
string[] dirs;
int lastIndex;
GetPathComponents(path, out dirs, out lastIndex);
var realPath = new StringBuilder();
if (dirs.Length > 0)
{
var dir = UnixPath.IsPathRooted(path) ? "/" : "";
dir += dirs[0];
realPath.Append(GetRealPath(dir));
}
for (var i = 1; i < lastIndex; ++i)
{
realPath.Append("/").Append(dirs[i]);
var realSubPath = GetRealPath(realPath.ToString());
realPath.Remove(0, realPath.Length);
realPath.Append(realSubPath);
}
path = realPath.ToString();
}
catch (Exception ex)
{
_logger.Debug(ex, string.Format("Failed to check for symlinks in the path {0}", path));
}
return base.GetMount(path); return base.GetMount(path);
} }
@@ -219,5 +249,65 @@ namespace NzbDrone.Mono.Disk
} }
private static void GetPathComponents(string path, out string[] components, out int lastIndex)
{
var dirs = path.Split(UnixPath.DirectorySeparatorChar);
var target = 0;
for (var i = 0; i < dirs.Length; ++i)
{
if (dirs[i] == "." || dirs[i] == string.Empty)
{
continue;
}
if (dirs[i] == "..")
{
if (target != 0)
{
target--;
}
else
{
target++;
}
}
else
{
dirs[target++] = dirs[i];
}
}
components = dirs;
lastIndex = target;
}
public string GetRealPath(string path)
{
do
{
var link = UnixPath.TryReadLink(path);
if (link == null)
{
var errno = Stdlib.GetLastError();
if (errno != Errno.EINVAL)
{
_logger.Trace("Checking path {0} for symlink returned error {1}, assuming it's not a symlink.", path, errno);
}
return path;
}
if (UnixPath.IsPathRooted(link))
{
path = link;
}
else
{
path = UnixPath.GetDirectoryName(path) + UnixPath.DirectorySeparatorChar + link;
path = UnixPath.GetCanonicalPath(path);
}
} while (true);
}
} }
} }

View File

@@ -6,23 +6,23 @@ using NLog;
namespace NzbDrone.Mono.Disk namespace NzbDrone.Mono.Disk
{ {
public interface ISymbolicLinkResolver public interface ISymbLinkResolver
{ {
string GetCompleteRealPath(string path); string GetCompletePath(string path);
} }
// Mono's own implementation doesn't handle exceptions very well. // Mono's own implementation doesn't handle exceptions very well.
// All of this code was copied from mono with minor changes. // All of this code was copied from mono with minor changes.
public class SymbolicLinkResolver : ISymbolicLinkResolver public class SymbLinkResolver : ISymbLinkResolver
{ {
private readonly Logger _logger; private readonly Logger _logger;
public SymbolicLinkResolver(Logger logger) public SymbLinkResolver(Logger logger)
{ {
_logger = logger; _logger = logger;
} }
public string GetCompleteRealPath(string path) public string GetCompletePath(string path)
{ {
if (path == null) return null; if (path == null) return null;

View File

@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14 # Visual Studio 14
VisualStudioVersion = 14.0.24720.0 VisualStudioVersion = 14.0.24720.0
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{57A04B72-8088-4F75-A582-1158CF8291F7}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{57A04B72-8088-4F75-A582-1158CF8291F7}"

View File

@@ -33,11 +33,15 @@ module.exports = Marionette.Layout.extend({
id : 'add-movies-screen' id : 'add-movies-screen'
}, },
initialize : function() { initialize : function(options) {
ProfileCollection.fetch(); ProfileCollection.fetch();
RootFolderCollection.fetch().done(function() { RootFolderCollection.fetch().done(function() {
RootFolderCollection.synced = true; RootFolderCollection.synced = true;
}); });
if (options.action === "search") {
this._addMovies(options);
}
}, },
_toggleExisting : function(e) { _toggleExisting : function(e) {
@@ -50,7 +54,7 @@ module.exports = Marionette.Layout.extend({
onShow : function() { onShow : function() {
this.workspace.show(new AddMoviesView()); this.workspace.show(new AddMoviesView(this.options));
this.ui.$existing.hide(); this.ui.$existing.hide();
}, },
@@ -72,8 +76,8 @@ module.exports = Marionette.Layout.extend({
AppLayout.modalRegion.show(this.rootFolderLayout); AppLayout.modalRegion.show(this.rootFolderLayout);
}, },
_addMovies : function() { _addMovies : function(options) {
this.workspace.show(new AddMoviesView()); this.workspace.show(new AddMoviesView(options));
}, },
_addFromList : function() { _addFromList : function() {

View File

@@ -26,7 +26,6 @@ module.exports = Marionette.Layout.extend({
}, },
initialize : function(options) { initialize : function(options) {
this.isExisting = options.isExisting; this.isExisting = options.isExisting;
this.collection = new AddMoviesCollection(); this.collection = new AddMoviesCollection();
@@ -49,6 +48,11 @@ module.exports = Marionette.Layout.extend({
}); });
this.throttledSearch = _.debounce(this.search, 1000, { trailing : true }).bind(this); this.throttledSearch = _.debounce(this.search, 1000, { trailing : true }).bind(this);
if (options.action === "search") {
this.search({term: options.query});
}
}, },
onRender : function() { onRender : function() {

View File

@@ -0,0 +1,59 @@
var Backgrid = require('backgrid');
var Marionette = require('marionette');
require('bootstrap');
module.exports = Backgrid.Cell.extend({
className : 'edition-cell',
//template : 'Cells/EditionCellTemplate',
render : function() {
var flags = this.model.get("indexerFlags");
if (!flags) {
return this;
}
var html = "";
if (flags) {
_.each(flags, function(flag){
var addon = "";
var title = "";
switch (flag) {
case "G_Freeleech":
addon = "⬇";
title = "Freeleech";
break;
case "G_Halfleech":
addon = "⇩";
title = "50% Freeleech";
break;
case "G_DoubleUpload":
addon = "⬆";
title = "Double upload";
break;
case "PTP_Golden":
addon = "🍿";
title = "Golden";
break;
case "PTP_Approved":
addon = "✔";
title = "Approved by PTP"
break;
case "HDB_Internal":
addon = "⭐️";
title = "HDBits Internal";
break;
}
if (addon != "") {
html += "<span title='{0}'>{1}</span> ".format(title, addon);
}
});
}
this.$el.html(html);
return this;
}
});

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