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

Compare commits

...

120 Commits

Author SHA1 Message Date
Levi Wilcox a0486b54a6 Fixed design issue when deleting css bug (#1480) Fixes #1475 2017-05-11 22:05:34 +02:00
Leonardo Galli 7ed0db10cb 10 Movies are now shown on discover as well as search results.
Show more should also be more consistent now.
2017-05-09 22:28:15 +02:00
Leonardo Galli e94591a290 Hotfix for when ignored movies would appear again after clicking on show more. 2017-05-09 21:28:22 +02:00
Leonardo Galli fccd02a0ca Merge remote-tracking branch 'origin/develop' into develop 2017-05-09 20:44:43 +02:00
Leonardo Galli b49f0e70ed Merge branch 'feature/better-import-exclusions' into develop 2017-05-09 20:44:17 +02:00
Leonardo Galli fc1585e900 Completely overhauled how import exclusions work.
Currently new exclusions can only be added when adding new movies or deleting old ones. Not manually in the settings menu.
Movies can now be hidden in the new discover feature by using the new import exclusions!
2017-05-09 20:44:07 +02:00
Leonardo Galli b36ac091fc Fix appveyor build 2017-05-09 19:49:30 +02:00
Leonardo Galli ab28bfead2 Merge branch 'develop' into feature/better-import-exclusions 2017-05-09 16:55:19 +02:00
Leonardo Galli 3ab3fbfd57 Added ability to discover new movies based on upcoming blurays as well as popular movies (borrowed from steven lu :)) 2017-05-09 16:46:19 +02:00
Leonardo Galli d133ee3143 Hopefully more logging to catch errors better. 2017-05-08 18:46:03 +02:00
Leonardo Galli 98b6932ffe Merge remote-tracking branch 'origin/develop' into develop 2017-05-08 18:07:38 +02:00
Leonardo Galli 58e81a916c Fixed error when language is present in title, but has dots instead of spaces. For example The.Danish.Girl.2015 2017-05-06 15:26:35 +02:00
PatrickGHanna 817f48448c Fix: A small bug fix for items loading as undefined in organize modal. Movie titles should now show up correctly. (#1424) 2017-05-06 12:31:41 +02:00
hotio 1eca179b4e Update Kodi icon, fixes #1464 (#1492) 2017-05-06 12:30:36 +02:00
Leonardo Galli b05d505bce Fixed Final in titles parsing as an edition. 2017-05-02 22:15:03 +02:00
Leonardo Galli ace426e69f Added initial migration. 2017-04-30 14:05:41 +02:00
Mitchell Cash a57c9917cc Fixed: Radarr not importing torrents in Vuze if the torrent already finished seeding and was stopped (#1471) 2017-04-30 13:14:49 +02:00
Mitchell Cash 9e84b4a782 Fixed: Incorrect imports with Vuze when torrent contains a single file. (#1470) 2017-04-30 10:37:51 +02:00
Mitchell Cash 494ef16735 Fixed: Sonarr UI Authentication cookie should be placed on path (UrlBase) instead of domain alone. Fixes ##1451 2017-04-30 10:30:59 +02:00
Leonardo Galli 5a0f02007f Added trailer links to the discovery page. 2017-04-28 14:14:02 +02:00
Leonardo Galli b1025e7229 Use Post for tmdbids request, to avoid too long URIs. 2017-04-28 13:14:00 +02:00
Leonardo Galli 446d661345 Added discovery tab based on tmdb recommendations based on your existing movies. (#1450)
Keep scroll position on more in search result view.
Added TMDB score to search results.
2017-04-28 11:04:30 +02:00
David Pooley 3eb351823e Tidy up layout of buttons on the Add Movies page for mobile/tablet (#1454) 2017-04-28 10:59:55 +02:00
morberg 835a7cffa1 Rename Sonarr to Radarr for OSX App 2017-04-26 18:44:57 +02:00
Leonardo Galli e728330ce4 Minor text fixes. 2017-04-26 15:45:06 +02:00
Leonardo Galli 6f3118c142 Merge remote-tracking branch 'origin/develop' into develop 2017-04-26 15:17:49 +02:00
Leonardo Galli b568072140 Change default page size to 250. Should help with safari timeouts. 2017-04-26 15:17:42 +02:00
Leonardo Galli 7db92c6bcf Enable automatic renaming, according to naming scheme, of movie folder after creation of the movie. (#1349)
Please test everything you can about this and report back if everything still works correctly.
2017-04-26 13:31:55 +02:00
Leonardo Galli f1e8a9acfc Fix for error when clicking Rescan Drone Folder 2017-04-19 22:04:34 +02:00
Leonardo Galli dae389ce64 Fix for error when trying to manually import. 2017-04-19 22:03:52 +02:00
Leonardo Galli 7c3d8c8ff9 Added multiple new editions such as FanEdit, Anniversary and 2in1. 2017-04-19 21:02:01 +02:00
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
188 changed files with 8623 additions and 1420 deletions
+4
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
+12 -5
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)
+9
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
+16 -6
View File
@@ -104,11 +104,13 @@ Task("Compile").Does(() => {
}); });
Task("Gulp").Does(() => { Task("Gulp").Does(() => {
Npm NpmInstall(new NpmInstallSettings {
.WithLogLevel(NpmLogLevel.Silent) LogLevel = NpmLogLevel.Silent,
.FromPath(".") WorkingDirectory = "./",
.Install() Production = true
.RunScript("build"); });
NpmRunScript("build");
}); });
Task("PackageMono").Does(() => { Task("PackageMono").Does(() => {
@@ -167,7 +169,7 @@ Task("PackageOsx").Does(() => {
CopyFiles(sourceFolder + "/Libraries/MediaInfo/*.dylib", outputFolderOsx); CopyFiles(sourceFolder + "/Libraries/MediaInfo/*.dylib", outputFolderOsx);
// Adding Startup script // Adding Startup script
CopyFile("./osx/Sonarr", outputFolderOsx + "/Sonarr"); CopyFile("./osx/Radarr", outputFolderOsx + "/Radarr");
}); });
Task("PackageOsxApp").Does(() => { Task("PackageOsxApp").Does(() => {
@@ -264,6 +266,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 +302,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")
+3 -3
View File
@@ -181,7 +181,7 @@ PackageOsx()
cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderOsx cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderOsx
echo "Adding Startup script" echo "Adding Startup script"
cp ./osx/Sonarr $outputFolderOsx cp ./osx/Radarr $outputFolderOsx
echo "##teamcity[progressFinish 'Creating OS X Package']" echo "##teamcity[progressFinish 'Creating OS X Package']"
} }
@@ -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
+44
View File
@@ -0,0 +1,44 @@
input1 = """Prometheus.Special.Edition.Fan Edit.2012..BRRip.x264.AAC-m2g
Star Wars Episode IV - A New Hope (Despecialized) 1999.mkv
Prometheus.(Special.Edition.Remastered).2012.[Bluray-1080p].mkv
Prometheus Extended 2012
Prometheus Extended Directors Cut Fan Edit 2012
Prometheus Director's Cut 2012
Prometheus Directors Cut 2012
Prometheus.(Extended.Theatrical.Version.IMAX).BluRay.1080p.2012.asdf
2001 A Space Odyssey Director's Cut (1968).mkv
2001: A Space Odyssey (Extended Directors Cut FanEdit) Bluray 1080p 1968
A Fake Movie 2035 Directors 2012.mkv
Blade Runner Director's Cut 2049.mkv
Prometheus 50th Anniversary Edition 2012.mkv
Movie 2in1 2012.mkv
Movie IMAX 2012.mkv"""
output1 = """Special.Edition.Fan Edit BRRip.x264.AAC-m2g
Despecialized mkv
Special.Edition.Remastered Bluray-1080p].mkv
Extended mkv
Extended Directors Cut Fan Edit mkv
Director's Cut mkv
Directors Cut mkv
Extended.Theatrical.Version.IMAX asdf
Director's Cut mkv
Extended Directors Cut FanEdit mkv
Directors mkv
Director's Cut mkv
50th Anniversary Edition mkv
2in1 mkv
IMAX mkv"""
inputs = input1.split("\n")
outputs = output1.split("\n")
real_o = []
for output in outputs:
real_o.append(output.split(" ")[0].replace(".", " ").strip())
count = 0
for inp in inputs:
o = real_o[count]
print "[TestCase(\"{0}\", \"{1}\")]".format(inp, o)
count += 1
+1 -1
View File
@@ -5,7 +5,7 @@ DIR=$(cd "$(dirname "$0")"; pwd)
#change these values to match your app #change these values to match your app
EXE_PATH="$DIR/Radarr.exe" EXE_PATH="$DIR/Radarr.exe"
APPNAME="Sonarr" APPNAME="Radarr"
#set up environment #set up environment
if [[ -x '/opt/local/bin/mono' ]]; then if [[ -x '/opt/local/bin/mono' ]]; then
+1 -1
View File
@@ -11,7 +11,7 @@
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>English</string> <string>English</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>Sonarr</string> <string>Radarr</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>radarr.icns</string> <string>radarr.icns</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
+38 -29
View File
@@ -23,20 +23,19 @@ 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'
outputFolderOsx='./_output_osx' outputFolderOsx='./_output_osx'
outputFolderOsxApp='./_output_osx_app' outputFolderOsxApp='./_output_osx_app'
tr -d "\r" < $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr > $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr2 tr -d "\r" < $outputFolderOsxApp/Radarr.app/Contents/MacOS/Radarr > $outputFolderOsxApp/Radarr.app/Contents/MacOS/Radarr2
rm $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr rm $outputFolderOsxApp/Radarr.app/Contents/MacOS/Radarr
chmod +x $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr2 chmod +x $outputFolderOsxApp/Radarr.app/Contents/MacOS/Radarr2
mv $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr2 $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr >& error.log mv $outputFolderOsxApp/Radarr.app/Contents/MacOS/Radarr2 $outputFolderOsxApp/Radarr.app/Contents/MacOS/Radarr >& 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/*
@@ -44,25 +43,35 @@ if [ $runtime = "dotnet" ] ; then
./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
+16 -16
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
@@ -70,6 +70,7 @@ namespace NzbDrone.Api.Authentication
{ {
RedirectUrl = _configFileProvider.UrlBase + "/login", RedirectUrl = _configFileProvider.UrlBase + "/login",
UserMapper = _authenticationService, UserMapper = _authenticationService,
Path = _configFileProvider.UrlBase,
CryptographyConfiguration = cryptographyConfiguration CryptographyConfiguration = cryptographyConfiguration
}); });
} }
+12 -2
View File
@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NLog;
using NzbDrone.Api.Extensions; using NzbDrone.Api.Extensions;
using NzbDrone.Api.Validation; using NzbDrone.Api.Validation;
using NzbDrone.Common; using NzbDrone.Common;
@@ -17,14 +18,17 @@ namespace NzbDrone.Api.Commands
{ {
private readonly IManageCommandQueue _commandQueueManager; private readonly IManageCommandQueue _commandQueueManager;
private readonly IServiceFactory _serviceFactory; private readonly IServiceFactory _serviceFactory;
private readonly Logger _logger;
public CommandModule(IManageCommandQueue commandQueueManager, public CommandModule(IManageCommandQueue commandQueueManager,
IBroadcastSignalRMessage signalRBroadcaster, IBroadcastSignalRMessage signalRBroadcaster,
IServiceFactory serviceFactory) IServiceFactory serviceFactory,
Logger logger)
: base(signalRBroadcaster) : base(signalRBroadcaster)
{ {
_commandQueueManager = commandQueueManager; _commandQueueManager = commandQueueManager;
_serviceFactory = serviceFactory; _serviceFactory = serviceFactory;
_logger = logger;
GetResourceById = GetCommand; GetResourceById = GetCommand;
CreateResource = StartCommand; CreateResource = StartCommand;
@@ -41,7 +45,13 @@ namespace NzbDrone.Api.Commands
private int StartCommand(CommandResource commandResource) private int StartCommand(CommandResource commandResource)
{ {
var commandType = _serviceFactory.GetImplementations(typeof(Command)) var commandType = _serviceFactory.GetImplementations(typeof(Command))
.Single(c => c.Name.Replace("Command", "").Equals(commandResource.Name, StringComparison.InvariantCultureIgnoreCase)); .SingleOrDefault(c => c.Name.Replace("Command", "").Equals(commandResource.Name, StringComparison.InvariantCultureIgnoreCase));
if (commandType == null)
{
_logger.Error("Found no matching command for {0}", commandResource.Name);
return 0;
}
dynamic command = Request.Body.FromJson(commandType); dynamic command = Request.Body.FromJson(commandType);
command.Trigger = CommandTrigger.Manual; command.Trigger = CommandTrigger.Manual;
@@ -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,
}; };
} }
} }
@@ -11,6 +11,8 @@ namespace NzbDrone.Api.Config
public bool AutoDownloadPropers { get; set; } public bool AutoDownloadPropers { get; set; }
public bool CreateEmptySeriesFolders { get; set; } public bool CreateEmptySeriesFolders { get; set; }
public FileDateType FileDate { get; set; } public FileDateType FileDate { get; set; }
public bool AutoRenameFolders { get; set; }
public bool PathsDefaultStatic { get; set; }
public bool SetPermissionsLinux { get; set; } public bool SetPermissionsLinux { get; set; }
public string FileChmod { get; set; } public string FileChmod { get; set; }
@@ -35,6 +37,8 @@ namespace NzbDrone.Api.Config
AutoDownloadPropers = model.AutoDownloadPropers, AutoDownloadPropers = model.AutoDownloadPropers,
CreateEmptySeriesFolders = model.CreateEmptySeriesFolders, CreateEmptySeriesFolders = model.CreateEmptySeriesFolders,
FileDate = model.FileDate, FileDate = model.FileDate,
AutoRenameFolders = model.AutoRenameFolders,
PathsDefaultStatic = model.PathsDefaultStatic,
SetPermissionsLinux = model.SetPermissionsLinux, SetPermissionsLinux = model.SetPermissionsLinux,
FileChmod = model.FileChmod, FileChmod = model.FileChmod,
+2 -1
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,
+4 -1
View File
@@ -46,11 +46,14 @@ namespace NzbDrone.Api.EpisodeFiles
return movie.ToResource(); return movie.ToResource();
} }
private void SetQuality(MovieFileResource movieFileResource) private void SetQuality(MovieFileResource movieFileResource)
{ {
var movieFile = _mediaFileService.GetMovie(movieFileResource.Id); var movieFile = _mediaFileService.GetMovie(movieFileResource.Id);
movieFile.Quality = movieFileResource.Quality; movieFile.Quality = movieFileResource.Quality;
_mediaFileService.Update(movieFile); _mediaFileService.Update(movieFile);
BroadcastResourceChange(ModelAction.Updated, movieFile.Id);
} }
private void DeleteMovieFile(int id) private void DeleteMovieFile(int id)
@@ -0,0 +1,45 @@
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Api.ClientSchema;
using NzbDrone.Core.NetImport;
using NzbDrone.Core.NetImport.ImportExclusions;
using NzbDrone.Core.Validation.Paths;
namespace NzbDrone.Api.NetImport
{
public class ImportExclusionsModule : NzbDroneRestModule<ImportExclusionsResource>
{
private readonly IImportExclusionsService _exclusionService;
public ImportExclusionsModule(NetImportFactory netImportFactory, IImportExclusionsService exclusionService) : base("exclusions")
{
_exclusionService = exclusionService;
GetResourceAll = GetAll;
CreateResource = AddExclusion;
DeleteResource = RemoveExclusion;
GetResourceById = GetById;
}
public List<ImportExclusionsResource> GetAll()
{
return _exclusionService.GetAllExclusions().ToResource();
}
public ImportExclusionsResource GetById(int id)
{
return _exclusionService.GetById(id).ToResource();
}
public int AddExclusion(ImportExclusionsResource exclusionResource)
{
var model = exclusionResource.ToModel();
return _exclusionService.AddExclusion(model).Id;
}
public void RemoveExclusion (int id)
{
_exclusionService.RemoveExclusion(new ImportExclusion { Id = id });
}
}
}
@@ -0,0 +1,46 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.NetImport;
using NzbDrone.Core.Tv;
namespace NzbDrone.Api.NetImport
{
public class ImportExclusionsResource : ProviderResource
{
//public int Id { get; set; }
public int TmdbId { get; set; }
public string MovieTitle { get; set; }
public int MovieYear { get; set; }
}
public static class ImportExclusionsResourceMapper
{
public static ImportExclusionsResource ToResource(this Core.NetImport.ImportExclusions.ImportExclusion model)
{
if (model == null) return null;
return new ImportExclusionsResource
{
Id = model.Id,
TmdbId = model.TmdbId,
MovieTitle = model.MovieTitle,
MovieYear = model.MovieYear
};
}
public static List<ImportExclusionsResource> ToResource(this IEnumerable<Core.NetImport.ImportExclusions.ImportExclusion> exclusions)
{
return exclusions.Select(ToResource).ToList();
}
public static Core.NetImport.ImportExclusions.ImportExclusion ToModel(this ImportExclusionsResource resource)
{
return new Core.NetImport.ImportExclusions.ImportExclusion
{
TmdbId = resource.TmdbId,
MovieTitle = resource.MovieTitle,
MovieYear = resource.MovieYear
};
}
}
}
+3
View File
@@ -270,6 +270,9 @@
<Compile Include="Wanted\MissingModule.cs" /> <Compile Include="Wanted\MissingModule.cs" />
<Compile Include="Wanted\MovieCutoffModule.cs" /> <Compile Include="Wanted\MovieCutoffModule.cs" />
<Compile Include="Wanted\MovieMissingModule.cs" /> <Compile Include="Wanted\MovieMissingModule.cs" />
<Compile Include="Series\MovieDiscoverModule.cs" />
<Compile Include="NetImport\ImportExclusionsModule.cs" />
<Compile Include="NetImport\ImportExclusionsResource.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="app.config" /> <None Include="app.config" />
+1 -1
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();
} }
@@ -0,0 +1,44 @@
using System.Collections.Generic;
using Nancy;
using NzbDrone.Api.Extensions;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MetadataSource;
using System.Linq;
using System;
using NzbDrone.Api.REST;
namespace NzbDrone.Api.Movie
{
public class MovieDiscoverModule : NzbDroneRestModule<MovieResource>
{
private readonly IDiscoverNewMovies _searchProxy;
public MovieDiscoverModule(IDiscoverNewMovies searchProxy)
: base("/movies/discover")
{
_searchProxy = searchProxy;
Get["/{action?recommendations}"] = x => Search(x.action);
}
private Response Search(string action)
{
var imdbResults = _searchProxy.DiscoverNewMovies(action);
return MapToResource(imdbResults).AsResponse();
}
private static IEnumerable<MovieResource> MapToResource(IEnumerable<Core.Tv.Movie> movies)
{
foreach (var currentSeries in movies)
{
var resource = currentSeries.ToResource();
var poster = currentSeries.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
if (poster != null)
{
resource.RemotePoster = poster.Url;
}
yield return resource;
}
}
}
}
+30 -7
View File
@@ -17,6 +17,8 @@ using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Validation; using NzbDrone.Core.Validation;
using NzbDrone.SignalR; using NzbDrone.SignalR;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using Microsoft.CSharp.RuntimeBinder;
using Nancy;
namespace NzbDrone.Api.Movie namespace NzbDrone.Api.Movie
{ {
@@ -58,9 +60,12 @@ namespace NzbDrone.Api.Movie
GetResourceAll = AllMovie; GetResourceAll = AllMovie;
GetResourcePaged = GetMoviePaged; GetResourcePaged = GetMoviePaged;
GetResourceById = GetMovie; GetResourceById = GetMovie;
Get[TITLE_SLUG_ROUTE] = (options) => { Get[TITLE_SLUG_ROUTE] = GetByTitleSlug; /*(options) => {
return ReqResExtensions.AsResponse(GetByTitleSlug(options.slug)); return ReqResExtensions.AsResponse(GetByTitleSlug(options.slug), Nancy.HttpStatusCode.OK);
}; };*/
CreateResource = AddMovie; CreateResource = AddMovie;
UpdateResource = UpdateMovie; UpdateResource = UpdateMovie;
DeleteResource = DeleteMovie; DeleteResource = DeleteMovie;
@@ -118,7 +123,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)
@@ -145,9 +150,27 @@ namespace NzbDrone.Api.Movie
return moviesResources; return moviesResources;
} }
private MovieResource GetByTitleSlug(string slug) private Response GetByTitleSlug(dynamic options)
{ {
return MapToResource(_moviesService.FindByTitleSlug(slug)); var slug = "";
try
{
slug = options.slug;
// do stuff with x
}
catch (RuntimeBinderException)
{
return new NotFoundResponse();
}
try
{
return MapToResource(_moviesService.FindByTitleSlug(slug)).AsResponse(Nancy.HttpStatusCode.OK);
}
catch (ModelNotFoundException)
{
return new NotFoundResponse();
}
} }
private int AddMovie(MovieResource moviesResource) private int AddMovie(MovieResource moviesResource)
@@ -213,7 +236,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)
+6 -2
View File
@@ -40,6 +40,7 @@ namespace NzbDrone.Api.Movie
//View & Edit //View & Edit
public string Path { get; set; } public string Path { get; set; }
public int ProfileId { get; set; } public int ProfileId { get; set; }
public MoviePathState PathState { get; set; }
//Editing Only //Editing Only
public bool Monitored { get; set; } public bool Monitored { get; set; }
@@ -120,7 +121,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
@@ -131,6 +132,7 @@ namespace NzbDrone.Api.Movie
Path = model.Path, Path = model.Path,
ProfileId = model.ProfileId, ProfileId = model.ProfileId,
PathState = model.PathState,
Monitored = model.Monitored, Monitored = model.Monitored,
MinimumAvailability = model.MinimumAvailability, MinimumAvailability = model.MinimumAvailability,
@@ -138,7 +140,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,
@@ -187,6 +189,7 @@ namespace NzbDrone.Api.Movie
Path = resource.Path, Path = resource.Path,
ProfileId = resource.ProfileId, ProfileId = resource.ProfileId,
PathState = resource.PathState,
Monitored = resource.Monitored, Monitored = resource.Monitored,
MinimumAvailability = resource.MinimumAvailability, MinimumAvailability = resource.MinimumAvailability,
@@ -217,6 +220,7 @@ namespace NzbDrone.Api.Movie
movie.Path = resource.Path; movie.Path = resource.Path;
movie.ProfileId = resource.ProfileId; movie.ProfileId = resource.ProfileId;
movie.PathState = resource.PathState;
movie.Monitored = resource.Monitored; movie.Monitored = resource.Monitored;
movie.MinimumAvailability = resource.MinimumAvailability; movie.MinimumAvailability = resource.MinimumAvailability;
@@ -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]
@@ -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));
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -80,5 +80,30 @@ namespace NzbDrone.Common.Extensions
{ {
return source.Select(predicate).ToList(); return source.Select(predicate).ToList();
} }
public static IEnumerable<T> DropLast<T>(this IEnumerable<T> source, int n)
{
if (source == null)
throw new ArgumentNullException("source");
if (n < 0)
throw new ArgumentOutOfRangeException("n",
"Argument n should be non-negative.");
return InternalDropLast(source, n);
}
private static IEnumerable<T> InternalDropLast<T>(IEnumerable<T> source, int n)
{
Queue<T> buffer = new Queue<T>(n + 1);
foreach (T x in source)
{
buffer.Enqueue(x);
if (buffer.Count == n + 1)
yield return buffer.Dequeue();
}
}
} }
} }
+1
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; }
@@ -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);
}
} }
} }
@@ -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());
} }
@@ -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());
} }
@@ -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();
} }
} }
} }
@@ -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();
}
} }
} }
@@ -145,8 +145,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
[TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading)] [TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading)]
[TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued)] [TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued)]
[TestCase(TransmissionTorrentStatus.Downloading, DownloadItemStatus.Downloading)] [TestCase(TransmissionTorrentStatus.Downloading, DownloadItemStatus.Downloading)]
[TestCase(TransmissionTorrentStatus.SeedingWait, DownloadItemStatus.Completed)] [TestCase(TransmissionTorrentStatus.SeedingWait, DownloadItemStatus.Downloading)]
[TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed)] [TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Downloading)]
public void GetItems_should_return_queued_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus) public void GetItems_should_return_queued_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus)
{ {
_queued.Status = apiStatus; _queued.Status = apiStatus;
@@ -160,7 +160,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
[TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued)] [TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued)]
[TestCase(TransmissionTorrentStatus.Downloading, DownloadItemStatus.Downloading)] [TestCase(TransmissionTorrentStatus.Downloading, DownloadItemStatus.Downloading)]
[TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed)] [TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Downloading)]
public void GetItems_should_return_downloading_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus) public void GetItems_should_return_downloading_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus)
{ {
_downloading.Status = apiStatus; _downloading.Status = apiStatus;
@@ -13,6 +13,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
[TestFixture] [TestFixture]
public class VuzeFixture : TransmissionFixtureBase<Vuze> public class VuzeFixture : TransmissionFixtureBase<Vuze>
{ {
[SetUp]
public void Setup_Vuze()
{
// Vuze never sets isFinished.
_completed.IsFinished = false;
}
[Test] [Test]
public void queued_item_should_have_required_properties() public void queued_item_should_have_required_properties()
{ {
@@ -147,8 +154,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
[TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading)] [TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading)]
[TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued)] [TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued)]
[TestCase(TransmissionTorrentStatus.Downloading, DownloadItemStatus.Downloading)] [TestCase(TransmissionTorrentStatus.Downloading, DownloadItemStatus.Downloading)]
[TestCase(TransmissionTorrentStatus.SeedingWait, DownloadItemStatus.Completed)] [TestCase(TransmissionTorrentStatus.SeedingWait, DownloadItemStatus.Downloading)]
[TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed)] [TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Downloading)]
public void GetItems_should_return_queued_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus) public void GetItems_should_return_queued_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus)
{ {
_queued.Status = apiStatus; _queued.Status = apiStatus;
@@ -162,7 +169,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
[TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued)] [TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued)]
[TestCase(TransmissionTorrentStatus.Downloading, DownloadItemStatus.Downloading)] [TestCase(TransmissionTorrentStatus.Downloading, DownloadItemStatus.Downloading)]
[TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed)] [TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Downloading)]
public void GetItems_should_return_downloading_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus) public void GetItems_should_return_downloading_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus)
{ {
_downloading.Status = apiStatus; _downloading.Status = apiStatus;
@@ -177,7 +184,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
[TestCase(TransmissionTorrentStatus.Stopped, DownloadItemStatus.Completed, false)] [TestCase(TransmissionTorrentStatus.Stopped, DownloadItemStatus.Completed, false)]
[TestCase(TransmissionTorrentStatus.CheckWait, DownloadItemStatus.Downloading, true)] [TestCase(TransmissionTorrentStatus.CheckWait, DownloadItemStatus.Downloading, true)]
[TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading, true)] [TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading, true)]
[TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Completed, true)] [TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued, true)]
[TestCase(TransmissionTorrentStatus.SeedingWait, DownloadItemStatus.Completed, true)] [TestCase(TransmissionTorrentStatus.SeedingWait, DownloadItemStatus.Completed, true)]
[TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed, true)] [TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed, true)]
public void GetItems_should_return_completed_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus, bool expectedReadOnly) public void GetItems_should_return_completed_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus, bool expectedReadOnly)
@@ -294,7 +301,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
} }
[Test] [Test]
public void should_have_correct_output_directory() public void should_have_correct_output_directory_for_multifile_torrents()
{ {
WindowsOnly(); WindowsOnly();
@@ -311,5 +318,25 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
items.First().OutputPath.Should().Be(@"C:\Downloads\" + _title); items.First().OutputPath.Should().Be(@"C:\Downloads\" + _title);
} }
[Test]
public void should_have_correct_output_directory_for_singlefile_torrents()
{
WindowsOnly();
var fileName = _title + ".mkv";
_downloading.Name = fileName;
_downloading.DownloadDir = @"C:/Downloads";
GivenTorrents(new List<TransmissionTorrent>
{
_downloading
});
var items = Subject.GetItems().ToList();
items.Should().HaveCount(1);
items.First().OutputPath.Should().Be(@"C:\Downloads\" + fileName);
}
} }
} }
File diff suppressed because it is too large Load Diff
@@ -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
}
}
] ]
} }
@@ -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
@@ -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);
} }
} }
} }
@@ -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);
}
} }
} }
@@ -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);
}
}
}
@@ -296,7 +296,9 @@
<Compile Include="OrganizerTests\FileNameBuilderTests\CleanTitleFixture.cs" /> <Compile Include="OrganizerTests\FileNameBuilderTests\CleanTitleFixture.cs" />
<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="OrganizerTests\GetMovieFolderFixture.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 +387,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 +579,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}" />
@@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using FizzWare.NBuilder; using FizzWare.NBuilder;
@@ -734,5 +734,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile) Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be(releaseGroup); .Should().Be(releaseGroup);
} }
} }
} }
@@ -0,0 +1,40 @@
using NUnit.Framework;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Test.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NzbDrone.Core.Tv;
using FluentAssertions;
namespace NzbDrone.Core.Test.OrganizerTests
{
[TestFixture]
public class GetMovieFolderFixture : CoreTest<FileNameBuilder>
{
private NamingConfig namingConfig;
[SetUp]
public void Setup()
{
namingConfig = NamingConfig.Default;
Mocker.GetMock<INamingConfigService>()
.Setup(c => c.GetConfig()).Returns(namingConfig);
}
[TestCase("Arrival", 2016, "{Movie Title} ({Release Year})", "Arrival (2016)")]
[TestCase("The Big Short", 2015, "{Movie TitleThe} ({Release Year})", "Big Short, The (2015)")]
[TestCase("The Big Short", 2015, "{Movie Title} ({Release Year})", "The Big Short (2015)")]
public void should_use_movieFolderFormat_to_build_folder_name(string movieTitle, int year, string format, string expected)
{
namingConfig.MovieFolderFormat = format;
var movie = new Movie { Title = movieTitle, Year = year };
Subject.GetMovieFolder(movie).Should().Be(expected);
}
}
}
@@ -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,8 @@ 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")]
[TestCase("Resident.Evil.The.Final.Chapter.2016", "Resident Evil The Final Chapter")]
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);
@@ -86,9 +88,48 @@ namespace NzbDrone.Core.Test.ParserTests
} }
[TestCase("The Danish Girl 2015")] [TestCase("The Danish Girl 2015")]
[TestCase("The.Danish.Girl.2015.1080p.BluRay.x264.DTS-HD.MA.5.1-RARBG")]
public void should_not_parse_language_in_movie_title(string postTitle) public void should_not_parse_language_in_movie_title(string postTitle)
{ {
Parser.Parser.ParseMovieTitle(postTitle).Language.Should().Be(Language.English); Parser.Parser.ParseMovieTitle(postTitle).Language.Should().Be(Language.English);
} }
[TestCase("Prometheus 2012 Directors Cut", "Directors Cut")]
[TestCase("Star Wars Episode IV - A New Hope 1999 (Despecialized).mkv", "Despecialized")]
[TestCase("Prometheus.2012.(Special.Edition.Remastered).[Bluray-1080p].mkv", "Special Edition Remastered")]
[TestCase("Prometheus 2012 Extended", "Extended")]
[TestCase("Prometheus 2012 Extended Directors Cut Fan Edit", "Extended Directors Cut Fan Edit")]
[TestCase("Prometheus 2012 Director's Cut", "Director's Cut")]
[TestCase("Prometheus 2012 Directors Cut", "Directors Cut")]
[TestCase("Prometheus.2012.(Extended.Theatrical.Version.IMAX).BluRay.1080p.2012.asdf", "Extended Theatrical Version IMAX")]
[TestCase("2001 A Space Odyssey (1968) Director's Cut .mkv", "Director's Cut")]
[TestCase("2001: A Space Odyssey 1968 (Extended Directors Cut FanEdit)", "Extended Directors Cut FanEdit")]
[TestCase("A Fake Movie 2035 2012 Directors.mkv", "Directors")]
[TestCase("Blade Runner 2049 Director's Cut.mkv", "Director's Cut")]
[TestCase("Prometheus 2012 50th Anniversary Edition.mkv", "50th Anniversary Edition")]
[TestCase("Movie 2012 2in1.mkv", "2in1")]
[TestCase("Movie 2012 IMAX.mkv", "IMAX")]
[TestCase("Movie 2012 Restored.mkv", "Restored")]
[TestCase("Prometheus.Special.Edition.Fan Edit.2012..BRRip.x264.AAC-m2g", "Special Edition Fan Edit")]
[TestCase("Star Wars Episode IV - A New Hope (Despecialized) 1999.mkv", "Despecialized")]
[TestCase("Prometheus.(Special.Edition.Remastered).2012.[Bluray-1080p].mkv", "Special Edition Remastered")]
[TestCase("Prometheus Extended 2012", "Extended")]
[TestCase("Prometheus Extended Directors Cut Fan Edit 2012", "Extended Directors Cut Fan Edit")]
[TestCase("Prometheus Director's Cut 2012", "Director's Cut")]
[TestCase("Prometheus Directors Cut 2012", "Directors Cut")]
[TestCase("Prometheus.(Extended.Theatrical.Version.IMAX).BluRay.1080p.2012.asdf", "Extended Theatrical Version IMAX")]
[TestCase("2001 A Space Odyssey Director's Cut (1968).mkv", "Director's Cut")]
[TestCase("2001: A Space Odyssey (Extended Directors Cut FanEdit) Bluray 1080p 1968", "Extended Directors Cut FanEdit")]
[TestCase("A Fake Movie 2035 Directors 2012.mkv", "Directors")]
[TestCase("Blade Runner Director's Cut 2049.mkv", "Director's Cut")]
[TestCase("Prometheus 50th Anniversary Edition 2012.mkv", "50th Anniversary Edition")]
[TestCase("Movie 2in1 2012.mkv", "2in1")]
[TestCase("Movie IMAX 2012.mkv", "IMAX")]
[TestCase("Fake Movie Final Cut 2016", "Final Cut")]
[TestCase("Fake Movie 2016 Final Cut ", "Final Cut")]
public void should_parse_edition(string postTitle, string edition)
{
Parser.Parser.ParseMovieTitle(postTitle).Edition.Should().Be(edition);
}
} }
} }
@@ -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);
}
} }
} }
@@ -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);
}
}
}
@@ -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); }
@@ -273,6 +294,20 @@ namespace NzbDrone.Core.Configuration
set { SetValue("ExtraFileExtensions", value); } set { SetValue("ExtraFileExtensions", value); }
} }
public bool AutoRenameFolders
{
get { return GetValueBoolean("AutoRenameFolders", false); }
set { SetValue("AutoRenameFolders", value); }
}
public bool PathsDefaultStatic
{
get { return GetValueBoolean("PathsDefaultStatic", true); }
set { SetValue("PathsDefaultStatic", value); }
}
public bool SetPermissionsLinux public bool SetPermissionsLinux
{ {
get { return GetValueBoolean("SetPermissionsLinux", false); } get { return GetValueBoolean("SetPermissionsLinux", false); }
@@ -33,6 +33,8 @@ namespace NzbDrone.Core.Configuration
bool CopyUsingHardlinks { get; set; } bool CopyUsingHardlinks { get; set; }
bool EnableMediaInfo { get; set; } bool EnableMediaInfo { get; set; }
string ExtraFileExtensions { get; set; } string ExtraFileExtensions { get; set; }
bool AutoRenameFolders { get; set; }
bool PathsDefaultStatic { get; set; }
//Permissions (Media Management) //Permissions (Media Management)
bool SetPermissionsLinux { get; set; } bool SetPermissionsLinux { get; set; }
@@ -46,17 +48,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; }
@@ -0,0 +1,16 @@
using System.Data;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(136)]
public class add_pathstate_to_movies : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Movies").AddColumn("PathState").AsInt32().WithDefaultValue(2);
}
}
}
@@ -0,0 +1,60 @@
using System.Data;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
using System.Text;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text.RegularExpressions;
using System.Globalization;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(137)]
public class add_import_exclusions_table : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
if (!this.Schema.Schema("dbo").Table("ImportExclusions").Exists())
{
Create.TableForModel("ImportExclusions")
.WithColumn("TmdbId").AsInt64().NotNullable().Unique().PrimaryKey()
.WithColumn("MovieTitle").AsString().Nullable()
.WithColumn("MovieYear").AsInt64().Nullable().WithDefault(0);
}
Execute.WithConnection(AddExisting);
}
private void AddExisting(IDbConnection conn, IDbTransaction tran)
{
using (IDbCommand getSeriesCmd = conn.CreateCommand())
{
getSeriesCmd.Transaction = tran;
getSeriesCmd.CommandText = @"SELECT Key, Value FROM Config WHERE Key = 'importexclusions'";
TextInfo textInfo = new CultureInfo("en-US", false).TextInfo;
using (IDataReader seriesReader = getSeriesCmd.ExecuteReader())
{
while (seriesReader.Read())
{
var Key = seriesReader.GetString(0);
var Value = seriesReader.GetString(1);
var importExclusions = Value.Split(',').Select(x => {
return string.Format("(\"{0}\", \"{1}\")", Regex.Replace(x, @"^.*\-(.*)$", "$1"),
textInfo.ToTitleCase(string.Join(" ", x.Split('-').DropLast(1))));
}).ToList();
using (IDbCommand updateCmd = conn.CreateCommand())
{
updateCmd.Transaction = tran;
updateCmd.CommandText = "INSERT INTO ImportExclusions (tmdbid, MovieTitle) VALUES " + string.Join(", ", importExclusions);
updateCmd.ExecuteNonQuery();
}
}
}
}
}
}
}
@@ -35,6 +35,7 @@ using NzbDrone.Core.Extras.Others;
using NzbDrone.Core.Extras.Subtitles; using NzbDrone.Core.Extras.Subtitles;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.NetImport; using NzbDrone.Core.NetImport;
using NzbDrone.Core.NetImport.ImportExclusions;
namespace NzbDrone.Core.Datastore namespace NzbDrone.Core.Datastore
{ {
@@ -105,6 +106,8 @@ namespace NzbDrone.Core.Datastore
.Relationship() .Relationship()
.HasOne(s => s.Profile, s => s.ProfileId) .HasOne(s => s.Profile, s => s.ProfileId)
.HasOne(m => m.MovieFile, m => m.MovieFileId); .HasOne(m => m.MovieFile, m => m.MovieFileId);
Mapper.Entity<ImportExclusion>().RegisterModel("ImportExclusions");
Mapper.Entity<Episode>().RegisterModel("Episodes") Mapper.Entity<Episode>().RegisterModel("Episodes")
@@ -159,6 +162,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());
@@ -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;
}
} }
} }
@@ -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)
{ {
@@ -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))
@@ -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();
@@ -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;
@@ -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; }
@@ -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;
} }
} }
@@ -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");
}
} }
} }
} }
@@ -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;
}
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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();
} }
@@ -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;
@@ -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;
@@ -0,0 +1,7 @@
namespace NzbDrone.Core.Download.Clients.Sabnzbd.Responses
{
public class SabnzbdFullStatusResponse
{
public SabnzbdFullStatus Status { get; set; }
}
}
@@ -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);
}
} }
} }
@@ -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; }
}
}
@@ -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);
@@ -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; }
@@ -86,8 +86,9 @@ namespace NzbDrone.Core.Download.Clients.Transmission
item.Status = DownloadItemStatus.Warning; item.Status = DownloadItemStatus.Warning;
item.Message = torrent.ErrorString; item.Message = torrent.ErrorString;
} }
else if (torrent.Status == TransmissionTorrentStatus.Seeding || else if (torrent.LeftUntilDone == 0 && (torrent.Status == TransmissionTorrentStatus.Stopped ||
torrent.Status == TransmissionTorrentStatus.SeedingWait) torrent.Status == TransmissionTorrentStatus.Seeding ||
torrent.Status == TransmissionTorrentStatus.SeedingWait))
{ {
item.Status = DownloadItemStatus.Completed; item.Status = DownloadItemStatus.Completed;
} }
@@ -26,7 +26,19 @@ namespace NzbDrone.Core.Download.Clients.Vuze
protected override OsPath GetOutputPath(OsPath outputPath, TransmissionTorrent torrent) protected override OsPath GetOutputPath(OsPath outputPath, TransmissionTorrent torrent)
{ {
_logger.Debug("Vuze output directory: {0}", outputPath); // Vuze has similar behavior as uTorrent:
// - A multi-file torrent is downloaded in a job folder and 'outputPath' points to that directory directly.
// - A single-file torrent is downloaded in the root folder and 'outputPath' poinst to that root folder.
// We have to make sure the return value points to the job folder OR file.
if (outputPath == null || outputPath.FileName == torrent.Name)
{
_logger.Trace("Vuze output directory: {0}", outputPath);
}
else
{
outputPath = outputPath + torrent.Name;
_logger.Trace("Vuze output file: {0}", outputPath);
}
return outputPath; return outputPath;
} }
@@ -50,4 +62,4 @@ namespace NzbDrone.Core.Download.Clients.Vuze
public override string Name => "Vuze"; public override string Name => "Vuze";
} }
} }
@@ -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++)
@@ -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;
} }
} }
} }
@@ -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);
@@ -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);
@@ -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
@@ -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)");
} }
} }
} }
@@ -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();
@@ -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);
}); });
} }
} }
@@ -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,
}); });
} }
} }
@@ -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; }
@@ -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
{ {
@@ -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
}); });
} }
@@ -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;
@@ -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)}"));
} }
@@ -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
@@ -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();
@@ -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;
} }
} }
@@ -0,0 +1,20 @@
using NzbDrone.Core.Messaging.Commands;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.MediaFiles.Commands
{
public class RenameMovieFolderCommand : Command
{
public List<int> MovieIds { get; set; }
public override bool SendUpdatesToClient => true;
public RenameMovieFolderCommand(List<int> ids)
{
MovieIds = ids;
}
}
}
@@ -44,6 +44,8 @@ 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 IRenameMovieFileService _renameMovieFiles;
private readonly Logger _logger; private readonly Logger _logger;
public DiskScanService(IDiskProvider diskProvider, public DiskScanService(IDiskProvider diskProvider,
@@ -55,6 +57,8 @@ namespace NzbDrone.Core.MediaFiles
IMediaFileTableCleanupService mediaFileTableCleanupService, IMediaFileTableCleanupService mediaFileTableCleanupService,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
IMovieService movieService, IMovieService movieService,
IMovieFileRepository movieFileRepository,
IRenameMovieFileService renameMovieFiles,
Logger logger) Logger logger)
{ {
_diskProvider = diskProvider; _diskProvider = diskProvider;
@@ -66,6 +70,8 @@ namespace NzbDrone.Core.MediaFiles
_mediaFileTableCleanupService = mediaFileTableCleanupService; _mediaFileTableCleanupService = mediaFileTableCleanupService;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_movieService = movieService; _movieService = movieService;
_movieFileRepository = movieFileRepository;
_renameMovieFiles = renameMovieFiles;
_logger = logger; _logger = logger;
} }
@@ -132,18 +138,21 @@ namespace NzbDrone.Core.MediaFiles
public void Scan(Movie movie) public void Scan(Movie movie)
{ {
//Try renaming the movie path in case anything changed such as year, title or something else.
_renameMovieFiles.RenameMoviePath(movie, true);
var rootFolder = _diskProvider.GetParentFolder(movie.Path); var rootFolder = _diskProvider.GetParentFolder(movie.Path);
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 +164,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));
@@ -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;
}
} }
} }
@@ -0,0 +1,18 @@
using NzbDrone.Common.Messaging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.MediaFiles.Events
{
public class MovieFileUpdatedEvent : IEvent
{
public MovieFile MovieFile { get; private set; }
public MovieFileUpdatedEvent(MovieFile movieFile)
{
MovieFile = movieFile;
}
}
}
@@ -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);
@@ -30,6 +30,7 @@ namespace NzbDrone.Core.MediaFiles
private readonly IDiskTransferService _diskTransferService; private readonly IDiskTransferService _diskTransferService;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IMediaFileAttributeService _mediaFileAttributeService; private readonly IMediaFileAttributeService _mediaFileAttributeService;
private readonly IRecycleBinProvider _recycleBinProvider;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly IConfigService _configService; private readonly IConfigService _configService;
private readonly Logger _logger; private readonly Logger _logger;
@@ -40,6 +41,7 @@ namespace NzbDrone.Core.MediaFiles
IDiskTransferService diskTransferService, IDiskTransferService diskTransferService,
IDiskProvider diskProvider, IDiskProvider diskProvider,
IMediaFileAttributeService mediaFileAttributeService, IMediaFileAttributeService mediaFileAttributeService,
IRecycleBinProvider recycleBinProvider,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
IConfigService configService, IConfigService configService,
Logger logger) Logger logger)
@@ -50,6 +52,7 @@ namespace NzbDrone.Core.MediaFiles
_diskTransferService = diskTransferService; _diskTransferService = diskTransferService;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_mediaFileAttributeService = mediaFileAttributeService; _mediaFileAttributeService = mediaFileAttributeService;
_recycleBinProvider = recycleBinProvider;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_configService = configService; _configService = configService;
_logger = logger; _logger = logger;
@@ -116,6 +119,15 @@ namespace NzbDrone.Core.MediaFiles
_diskTransferService.TransferFile(movieFilePath, destinationFilePath, mode); _diskTransferService.TransferFile(movieFilePath, destinationFilePath, mode);
var oldMoviePath = movie.Path;
var newMoviePath = new OsPath(destinationFilePath).Directory.FullPath.TrimEnd(Path.DirectorySeparatorChar);
movie.Path = newMoviePath;
if (oldMoviePath != newMoviePath)
{
_movieService.UpdateMovie(movie);
}
movieFile.RelativePath = movie.Path.GetRelativePath(destinationFilePath); movieFile.RelativePath = movie.Path.GetRelativePath(destinationFilePath);
_updateMovieFileService.ChangeFileDateForFile(movieFile, movie); _updateMovieFileService.ChangeFileDateForFile(movieFile, movie);
@@ -132,6 +144,14 @@ namespace NzbDrone.Core.MediaFiles
_mediaFileAttributeService.SetFilePermissions(destinationFilePath); _mediaFileAttributeService.SetFilePermissions(destinationFilePath);
if(oldMoviePath != newMoviePath)
{
if (_diskProvider.GetFiles(oldMoviePath, SearchOption.AllDirectories).Count() == 0)
{
_recycleBinProvider.DeleteFolder(oldMoviePath);
}
}
return movieFile; return movieFile;
} }
@@ -143,7 +163,9 @@ namespace NzbDrone.Core.MediaFiles
private void EnsureMovieFolder(MovieFile movieFile, Movie movie, string filePath) private void EnsureMovieFolder(MovieFile movieFile, Movie movie, string filePath)
{ {
var movieFolder = Path.GetDirectoryName(filePath); var movieFolder = Path.GetDirectoryName(filePath);
movie.Path = movieFolder;
var rootFolder = new OsPath(movieFolder).Directory.FullPath; var rootFolder = new OsPath(movieFolder).Directory.FullPath;
var fileName = Path.GetFileName(filePath);
if (!_diskProvider.FolderExists(rootFolder)) if (!_diskProvider.FolderExists(rootFolder))
{ {
@@ -156,7 +178,7 @@ namespace NzbDrone.Core.MediaFiles
if (!_diskProvider.FolderExists(movieFolder)) if (!_diskProvider.FolderExists(movieFolder))
{ {
CreateFolder(movieFolder); CreateFolder(movieFolder);
newEvent.SeriesFolder = movieFolder; newEvent.MovieFolder = movieFolder;
changed = true; changed = true;
} }
@@ -13,23 +13,29 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Organizer; using NzbDrone.Core.Organizer;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.MediaFiles namespace NzbDrone.Core.MediaFiles
{ {
public interface IRenameMovieFileService public interface IRenameMovieFileService
{ {
List<RenameMovieFilePreview> GetRenamePreviews(int movieId); List<RenameMovieFilePreview> GetRenamePreviews(int movieId);
void RenameMoviePath(Movie movie, bool shouldRenameFiles);
} }
public class RenameMovieFileService : IRenameMovieFileService, public class RenameMovieFileService : IRenameMovieFileService,
IExecute<RenameMovieFilesCommand>, IExecute<RenameMovieFilesCommand>,
IExecute<RenameMovieCommand> IExecute<RenameMovieCommand>,
IExecute<RenameMovieFolderCommand>
{ {
private readonly IMovieService _movieService; private readonly IMovieService _movieService;
private readonly IMediaFileService _mediaFileService; private readonly IMediaFileService _mediaFileService;
private readonly IMoveMovieFiles _movieFileMover; private readonly IMoveMovieFiles _movieFileMover;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly IBuildFileNames _filenameBuilder; private readonly IBuildFileNames _filenameBuilder;
private readonly IConfigService _configService;
private readonly IDiskProvider _diskProvider;
private readonly IRecycleBinProvider _recycleBinProvider;
private readonly Logger _logger; private readonly Logger _logger;
public RenameMovieFileService(IMovieService movieService, public RenameMovieFileService(IMovieService movieService,
@@ -37,6 +43,9 @@ namespace NzbDrone.Core.MediaFiles
IMoveMovieFiles movieFileMover, IMoveMovieFiles movieFileMover,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
IBuildFileNames filenameBuilder, IBuildFileNames filenameBuilder,
IConfigService configService,
IRecycleBinProvider recycleBinProvider,
IDiskProvider diskProvider,
Logger logger) Logger logger)
{ {
_movieService = movieService; _movieService = movieService;
@@ -44,6 +53,9 @@ namespace NzbDrone.Core.MediaFiles
_movieFileMover = movieFileMover; _movieFileMover = movieFileMover;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_filenameBuilder = filenameBuilder; _filenameBuilder = filenameBuilder;
_configService = configService;
_recycleBinProvider = recycleBinProvider;
_diskProvider = diskProvider;
_logger = logger; _logger = logger;
} }
@@ -71,8 +83,9 @@ namespace NzbDrone.Core.MediaFiles
{ {
MovieId = movie.Id, MovieId = movie.Id,
MovieFileId = file.Id, MovieFileId = file.Id,
ExistingPath = file.RelativePath, ExistingPath = movieFilePath,
NewPath = movie.Path.GetRelativePath(newPath) //NewPath = movie.Path.GetRelativePath(newPath)
NewPath = newPath
}; };
} }
@@ -80,13 +93,19 @@ namespace NzbDrone.Core.MediaFiles
} }
private void RenameFiles(List<MovieFile> movieFiles, Movie movie) private void RenameFiles(List<MovieFile> movieFiles, Movie movie, string oldMoviePath = null)
{ {
var renamed = new List<MovieFile>(); var renamed = new List<MovieFile>();
foreach(var movieFile in movieFiles) if (oldMoviePath == null)
{ {
var movieFilePath = Path.Combine(movie.Path, movieFile.RelativePath); oldMoviePath = movie.Path;
}
foreach (var movieFile in movieFiles)
{
var oldMovieFilePath = Path.Combine(oldMoviePath, movieFile.RelativePath);
movieFile.Path = oldMovieFilePath;
try try
{ {
@@ -94,22 +113,64 @@ namespace NzbDrone.Core.MediaFiles
_movieFileMover.MoveMovieFile(movieFile, movie); _movieFileMover.MoveMovieFile(movieFile, movie);
_mediaFileService.Update(movieFile); _mediaFileService.Update(movieFile);
_movieService.UpdateMovie(movie);
renamed.Add(movieFile); renamed.Add(movieFile);
_logger.Debug("Renamed movie file: {0}", movieFile); _logger.Debug("Renamed movie file: {0}", movieFile);
} }
catch(SameFilenameException ex) catch (SameFilenameException ex)
{ {
_logger.Debug("File not renamed, source and destination are the same: {0}", ex.Filename); _logger.Debug("File not renamed, source and destination are the same: {0}", ex.Filename);
} }
catch(Exception ex) catch (Exception ex)
{ {
_logger.Error(ex, "Failed to rename file: " + movieFilePath); _logger.Error(ex, "Failed to rename file: " + oldMovieFilePath);
} }
} }
} }
public void RenameMoviePath(Movie movie, bool shouldRenameFiles = true)
{
var newFolder = _filenameBuilder.BuildMoviePath(movie);
if (newFolder != movie.Path && movie.PathState == MoviePathState.Dynamic)
{
if (!_configService.AutoRenameFolders)
{
_logger.Info("{0}'s movie should be {1} according to your naming config.", movie, newFolder);
return;
}
_logger.Info("{0}'s movie folder changed to: {1}", movie, newFolder);
var oldFolder = movie.Path;
movie.Path = newFolder;
if (shouldRenameFiles)
{
var movieFiles = _mediaFileService.GetFilesByMovie(movie.Id);
_logger.ProgressInfo("Renaming movie files for {0}", movie.Title);
RenameFiles(movieFiles, movie, oldFolder);
_logger.ProgressInfo("All movie files renamed for {0}", movie.Title);
}
_movieService.UpdateMovie(movie);
if (_diskProvider.GetFiles(oldFolder, SearchOption.AllDirectories).Count() == 0)
{
_recycleBinProvider.DeleteFolder(oldFolder);
}
}
if (movie.PathState == MoviePathState.StaticOnce)
{
movie.PathState = MoviePathState.Dynamic;
_movieService.UpdateMovie(movie);
}
}
public void Execute(RenameMovieFilesCommand message) public void Execute(RenameMovieFilesCommand message)
{ {
var movie = _movieService.GetMovie(message.MovieId); var movie = _movieService.GetMovie(message.MovieId);
@@ -134,5 +195,17 @@ namespace NzbDrone.Core.MediaFiles
} }
} }
public void Execute(RenameMovieFolderCommand message)
{
_logger.Debug("Renaming movie folder for selected movie if necessary");
var moviesToRename = _movieService.GetMovies(message.MovieIds);
foreach(var movie in moviesToRename)
{
var movieFiles = _mediaFileService.GetFilesByMovie(movie.Id);
_logger.ProgressInfo("Renaming movie folder for {0}", movie.Title);
RenameMoviePath(movie);
}
}
} }
} }
@@ -18,6 +18,7 @@ namespace NzbDrone.Core.MediaFiles
private readonly IMediaFileService _mediaFileService; private readonly IMediaFileService _mediaFileService;
private readonly IMoveEpisodeFiles _episodeFileMover; private readonly IMoveEpisodeFiles _episodeFileMover;
private readonly IMoveMovieFiles _movieFileMover; private readonly IMoveMovieFiles _movieFileMover;
private readonly IRenameMovieFileService _movieFileRenamer;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly Logger _logger; private readonly Logger _logger;
@@ -26,6 +27,7 @@ namespace NzbDrone.Core.MediaFiles
IMoveEpisodeFiles episodeFileMover, IMoveEpisodeFiles episodeFileMover,
IMoveMovieFiles movieFileMover, IMoveMovieFiles movieFileMover,
IDiskProvider diskProvider, IDiskProvider diskProvider,
IRenameMovieFileService movieFileRenamer,
Logger logger) Logger logger)
{ {
_recycleBinProvider = recycleBinProvider; _recycleBinProvider = recycleBinProvider;
@@ -33,6 +35,7 @@ namespace NzbDrone.Core.MediaFiles
_episodeFileMover = episodeFileMover; _episodeFileMover = episodeFileMover;
_movieFileMover = movieFileMover; _movieFileMover = movieFileMover;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_movieFileRenamer = movieFileRenamer;
_logger = logger; _logger = logger;
} }
@@ -57,6 +60,10 @@ namespace NzbDrone.Core.MediaFiles
_mediaFileService.Delete(existingFile, DeleteMediaFileReason.Upgrade); _mediaFileService.Delete(existingFile, DeleteMediaFileReason.Upgrade);
} }
//Temporary for correctly getting path
localMovie.Movie.MovieFileId = 1;
localMovie.Movie.MovieFile = movieFile;
if (copyOnly) if (copyOnly)
{ {
moveFileResult.MovieFile = _movieFileMover.CopyMovieFile(movieFile, localMovie); moveFileResult.MovieFile = _movieFileMover.CopyMovieFile(movieFile, localMovie);
@@ -66,6 +73,8 @@ namespace NzbDrone.Core.MediaFiles
moveFileResult.MovieFile = _movieFileMover.MoveMovieFile(movieFile, localMovie); moveFileResult.MovieFile = _movieFileMover.MoveMovieFile(movieFile, localMovie);
} }
//_movieFileRenamer.RenameMoviePath(localMovie.Movie, false);
return moveFileResult; return moveFileResult;
} }
@@ -0,0 +1,10 @@
using System.Collections.Generic;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.MetadataSource
{
public interface IDiscoverNewMovies
{
List<Movie> DiscoverNewMovies(string action);
}
}
@@ -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;
}
} }
} }
} }

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