1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-28 18:05:41 -04:00

Compare commits

...

89 Commits

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

closes #1775

* fixed sab tests.

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

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

* Fix pending release service, also fix the deduper

* Clean up the cleanup'er (housekeeping)

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

This reverts commit c03c13d924.

* Housekeeping updates, without breaking tests

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

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

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

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

* Added ImdbId to the release info from AwesomeHD

* Add ImdbId to release info for PTP and HDBits

* ImdbId is required for HDBits

* Added some error handling in SkyHook for TMDb

* Remove un-needed imports

* DIsable movie search after list sync (causing issues)

* small change

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

View File

@@ -1,5 +1,9 @@
**Description:**
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.
Please also try to include the following if you are reporting a bug

View File

@@ -1,5 +1,5 @@
<p align="center">
<img src="/Logo/text256.png" alt="Radarr">
<img src="/Logo/text256.png" alt="Radarr">
</p>
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
* QBittorrent, Deluge, rTorrent, Transmission and uTorrent download client (Other clients are coming)
* 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
### Planned Features
* Scanning PreDB to know when a new release is available
* Fixing the other Indexers and download clients
* 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, metadata)
* Downloading Metadata such as trailers or subtitles (\*)
* Adding metadata such as posters and information for Kodi and others to use (\*)
* Dynamically renaming folders with quality info, etc. (\*)
* 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)

View File

@@ -27,6 +27,7 @@ test:
artifacts:
- path: '_artifacts\*.zip'
- path: '_artifacts\*.exe'
- path: '_artifacts\*.tar.gz'
cache:
@@ -36,9 +37,17 @@ cache:
pull_requests:
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:
files:
- src/
- osx/
- gulp/
- logo/
- setup/
- appveyor.yml
- build-appveyor.cake

View File

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

View File

@@ -208,9 +208,9 @@ PackageTests()
find $sourceFolder -path $testSearchPattern -exec cp -r -u -T "{}" $testPackageFolder \;
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
mono $nuget install NUnit.ConsoleRunner -Version 3.2.0 -Output $testPackageFolder
mono $nuget install NUnit.Runners -Version 3.2.1 -Output $testPackageFolder
fi
cp $outputFolder/*.dll $testPackageFolder

View File

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

View File

@@ -1,35 +1,35 @@
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define AppName "Sonarr"
#define AppPublisher "Team Sonarr"
#define AppURL "https://sonarr.tv/"
#define ForumsURL "https://forums.sonarr.tv/"
#define AppExeName "NzbDrone.exe"
#define AppName "Radarr"
#define AppPublisher "Team Radarr"
#define AppURL "https://radarr.video/"
#define ForumsURL "https://github.com/Radarr/Radarr/issues"
#define AppExeName "Radarr.exe"
#define BuildNumber "2.0"
#define BuildNumber GetEnv('BUILD_NUMBER')
#define BranchName GetEnv('branch')
#define BuildVersion GetEnv('APPVEYOR_BUILD_VERSION')
#define BranchName GetEnv('APPVEYOR_REPO_BRANCH')
[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; (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}
AppVersion=2.0
AppVersion=0.2
AppPublisher={#AppPublisher}
AppPublisherURL={#AppURL}
AppSupportURL={#ForumsURL}
AppUpdatesURL={#AppURL}
DefaultDirName={commonappdata}\NzbDrone\bin
DefaultDirName={commonappdata}\Radarr\bin
DisableDirPage=yes
DefaultGroupName={#AppName}
DisableProgramGroupPage=yes
OutputBaseFilename=NzbDrone.{#BranchName}.{#BuildNumber}
OutputBaseFilename=Radarr.{#BranchName}.{#BuildVersion}.installer
SolidCompression=yes
AppCopyright=Creative Commons 3.0 License
AllowUNCPath=False
UninstallDisplayIcon={app}\NzbDrone.exe
UninstallDisplayIcon={app}\Radarr.exe
DisableReadyPage=True
CompressionThreads=2
Compression=lzma2/normal
@@ -44,7 +44,7 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "windowsService"; Description: "Install as a Windows Service"
[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
; 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"
[Run]
Filename: "{app}\nzbdrone.console.exe"; Parameters: "/u"; Flags: waituntilterminated;
Filename: "{app}\nzbdrone.console.exe"; Parameters: "/i"; Flags: waituntilterminated; Tasks: windowsService
Filename: "{app}\radarr.console.exe"; Parameters: "/u"; Flags: waituntilterminated;
Filename: "{app}\radarr.console.exe"; Parameters: "/i"; Flags: waituntilterminated; Tasks: windowsService
[UninstallRun]
Filename: "{app}\nzbdrone.console.exe"; Parameters: "/u"; Flags: waituntilterminated skipifdoesntexist
Filename: "{app}\radarr.console.exe"; Parameters: "/u"; Flags: waituntilterminated skipifdoesntexist

View File

@@ -8,7 +8,10 @@ namespace NzbDrone.Api.Config
public int MinimumAge { get; set; }
public int Retention { 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
@@ -20,7 +23,11 @@ namespace NzbDrone.Api.Config
MinimumAge = model.MinimumAge,
Retention = model.Retention,
RssSyncInterval = model.RssSyncInterval,
AvailabilityDelay = model.AvailabilityDelay,
PreferIndexerFlags = model.PreferIndexerFlags,
AvailabilityDelay = model.AvailabilityDelay,
AllowHardcodedSubs = model.AllowHardcodedSubs,
WhitelistedHardcodedSubs = model.WhitelistedHardcodedSubs,
};
}
}

View File

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

View File

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

View File

@@ -118,7 +118,7 @@ namespace NzbDrone.Api.Movie
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)
@@ -213,7 +213,7 @@ namespace NzbDrone.Api.Movie
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)

View File

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

View File

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

View File

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

View File

@@ -4,6 +4,7 @@
{
public static readonly HttpAccept Rss = new HttpAccept("application/rss+xml, text/rss+xml, application/xml, text/xml");
public static readonly HttpAccept 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 string Value { get; private set; }

View File

@@ -20,6 +20,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private RemoteEpisode parseResultMulti;
private RemoteEpisode parseResultSingle;
private Series series;
private Movie movie;
private RemoteMovie remoteMovie;
private QualityDefinition qualityType;
[SetUp]
@@ -28,6 +30,16 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
series = Builder<Series>.CreateNew()
.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
{
Series = series,
@@ -216,5 +228,17 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.IsSatisfiedBy(parseResultSingle, null).Accepted.Should().BeTrue();
}
[Test]
public void should_use_110_minutes_if_runtime_is_0()
{
movie.Runtime = 0;
remoteMovie.Movie = movie;
remoteMovie.Release.Size = 1095.Megabytes();
Subject.IsSatisfiedBy(remoteMovie, null).Accepted.Should().Be(true);
remoteMovie.Release.Size = 1105.Megabytes();
Subject.IsSatisfiedBy(remoteMovie, null).Accepted.Should().Be(false);
}
}
}

View File

@@ -275,7 +275,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
{ "default_destination", _defaultDestination },
};
Mocker.GetMock<IDownloadStationProxy>()
Mocker.GetMock<IDownloadStationInfoProxy>()
.Setup(v => v.GetConfig(It.IsAny<DownloadStationSettings>()))
.Returns(_downloadStationConfigItems);
}
@@ -311,7 +311,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
torrents = new List<DownloadStationTask>();
}
Mocker.GetMock<IDownloadStationProxy>()
Mocker.GetMock<IDownloadStationTaskProxy>()
.Setup(s => s.GetTasks(It.IsAny<DownloadStationSettings>()))
.Returns(torrents);
}
@@ -330,11 +330,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
.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>()))
.Callback(PrepareClientToReturnQueuedItem);
Mocker.GetMock<IDownloadStationProxy>()
Mocker.GetMock<IDownloadStationTaskProxy>()
.Setup(s => s.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DownloadStationSettings>()))
.Callback(PrepareClientToReturnQueuedItem);
}
@@ -352,7 +352,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
{
var tasks = new List<DownloadStationTask>() { _queued, _completed, _failed, _downloading, _seeding };
Mocker.GetMock<IDownloadStationProxy>()
Mocker.GetMock<IDownloadStationTaskProxy>()
.Setup(d => d.GetTasks(_settings))
.Returns(tasks);
@@ -372,7 +372,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
id.Should().NotBeNullOrEmpty();
Mocker.GetMock<IDownloadStationProxy>()
Mocker.GetMock<IDownloadStationTaskProxy>()
.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();
Mocker.GetMock<IDownloadStationProxy>()
Mocker.GetMock<IDownloadStationTaskProxy>()
.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();
Mocker.GetMock<IDownloadStationProxy>()
Mocker.GetMock<IDownloadStationTaskProxy>()
.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));
Mocker.GetMock<IDownloadStationProxy>()
Mocker.GetMock<IDownloadStationTaskProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, _settings), Times.Never());
}

View File

@@ -177,7 +177,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
{ "default_destination", _defaultDestination },
};
Mocker.GetMock<IDownloadStationProxy>()
Mocker.GetMock<IDownloadStationInfoProxy>()
.Setup(v => v.GetConfig(It.IsAny<DownloadStationSettings>()))
.Returns(_downloadStationConfigItems);
}
@@ -213,7 +213,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
nzbs = new List<DownloadStationTask>();
}
Mocker.GetMock<IDownloadStationProxy>()
Mocker.GetMock<IDownloadStationTaskProxy>()
.Setup(s => s.GetTasks(It.IsAny<DownloadStationSettings>()))
.Returns(nzbs);
}
@@ -233,7 +233,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
.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>()))
.Callback(PrepareClientToReturnQueuedItem);
}
@@ -242,7 +242,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
{
var tasks = new List<DownloadStationTask>() { _queued, _completed, _failed, _downloading, _seeding };
Mocker.GetMock<IDownloadStationProxy>()
Mocker.GetMock<IDownloadStationTaskProxy>()
.Setup(d => d.GetTasks(_settings))
.Returns(tasks);
}
@@ -260,7 +260,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
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());
}
@@ -277,7 +277,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
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());
}
@@ -293,7 +293,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
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());
}
@@ -370,7 +370,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
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());
}

View File

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

View File

@@ -23,6 +23,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
private SabnzbdHistory _failed;
private SabnzbdHistory _completed;
private SabnzbdConfig _config;
private SabnzbdFullStatus _fullStatus;
[SetUp]
public void Setup()
@@ -65,7 +66,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
{
Status = SabnzbdDownloadStatus.Failed,
Size = 1000,
Category = "tv",
Category = "tv",
Id = "sabnzbd_nzb12345",
Title = "Droned.1998.1080p.WEB-DL-DRONE"
}
@@ -80,7 +81,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
{
Status = SabnzbdDownloadStatus.Completed,
Size = 1000,
Category = "tv",
Category = "tv",
Id = "sabnzbd_nzb12345",
Title = "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>()
.Setup(s => s.GetConfig(It.IsAny<SabnzbdSettings>()))
.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()
@@ -166,7 +187,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
GivenQueue(_queued);
GivenHistory(null);
var result = Subject.GetItems().Single();
VerifyQueued(result);
@@ -387,23 +408,46 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
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", @"vv", @"Y:\nzbget\root\completed\vv")]
[TestCase(@"/nzbget/root", @"completed/downloads", @"vv", @"/nzbget/root/completed/downloads/vv")]
[TestCase(@"/nzbget/root", @"completed", @"vv", @"/nzbget/root/completed/vv")]
public void should_return_status_with_outputdir(string rootFolder, string completeDir, string categoryDir, string expectedDir)
[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_lt_2(string rootFolder, string completeDir, string categoryDir, string fullCompleteDir, string fullCategoryDir)
{
_fullStatus.CompleteDir = null;
_queued.DefaultRootFolder = rootFolder;
_config.Misc.complete_dir = completeDir;
_config.Categories.First().Dir = categoryDir;
GivenVersion("1.2.1");
GivenQueue(null);
var result = Subject.GetStatus();
result.IsLocalhost.Should().BeTrue();
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]
@@ -451,5 +495,73 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
result.IsValid.Should().BeTrue();
result.HasWarnings.Should().BeTrue();
}
[Test]
public void should_test_success_if_tv_sorting_disabled()
{
_config.Misc.enable_tv_sorting = false;
_config.Misc.tv_categories = null;
var result = new NzbDroneValidationResult(Subject.Test());
result.IsValid.Should().BeTrue();
}
[Test]
public void should_test_failed_if_tv_sorting_null()
{
_config.Misc.enable_tv_sorting = true;
_config.Misc.tv_categories = null;
var result = new NzbDroneValidationResult(Subject.Test());
result.IsValid.Should().BeFalse();
}
[Test]
public void should_test_failed_if_tv_sorting_empty()
{
_config.Misc.enable_tv_sorting = true;
_config.Misc.tv_categories = new string[0];
var result = new NzbDroneValidationResult(Subject.Test());
result.IsValid.Should().BeFalse();
}
[Test]
public void should_test_success_if_tv_sorting_contains_different_category()
{
_config.Misc.enable_tv_sorting = true;
_config.Misc.tv_categories = new[] { "tv-custom" };
var result = new NzbDroneValidationResult(Subject.Test());
result.IsValid.Should().BeTrue();
}
[Test]
public void should_test_failed_if_tv_sorting_contains_category()
{
_config.Misc.enable_tv_sorting = true;
_config.Misc.tv_categories = new[] { "tv" };
var result = new NzbDroneValidationResult(Subject.Test());
result.IsValid.Should().BeFalse();
}
[Test]
public void should_test_failed_if_tv_sorting_default_category()
{
Subject.Definition.Settings.As<SabnzbdSettings>().TvCategory = null;
_config.Misc.enable_tv_sorting = true;
_config.Misc.tv_categories = new[] { "Default" };
var result = new NzbDroneValidationResult(Subject.Test());
result.IsValid.Should().BeFalse();
}
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -105,11 +105,11 @@ namespace NzbDrone.Core.Configuration
set { SetValue("RssSyncInterval", value); }
}
public int AvailabilityDelay
{
get { return GetValueInt("AvailabilityDelay",0); }
set { SetValue("AvailabilityDelay", value); }
}
public int AvailabilityDelay
{
get { return GetValueInt("AvailabilityDelay",0); }
set { SetValue("AvailabilityDelay", value); }
}
public int NetImportSyncInterval
{
@@ -190,6 +190,27 @@ namespace NzbDrone.Core.Configuration
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
{
get { return GetValueBoolean("RemoveCompletedDownloads", false); }

View File

@@ -46,17 +46,22 @@ namespace NzbDrone.Core.Configuration
int RssSyncInterval { 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; }
string ListSyncLevel { get; set; }
string ImportExclusions { get; set; }
string ListSyncLevel { get; set; }
string ImportExclusions { get; set; }
string TraktAuthToken { get; set; }
string TraktRefreshToken { get; set; }
int TraktTokenExpiry { get; set; }
string NewTraktAuthToken { get; set; }
string NewTraktRefreshToken {get; set; }
int NewTraktTokenExpiry { get; set; }
string NewTraktAuthToken { get; set; }
string NewTraktRefreshToken {get; set; }
int NewTraktTokenExpiry { get; set; }
//UI
int FirstDayOfWeek { get; set; }

View File

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

View File

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

View File

@@ -1,10 +1,11 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
@@ -21,12 +22,14 @@ namespace NzbDrone.Core.DecisionEngine
{
private readonly IEnumerable<IDecisionEngineSpecification> _specifications;
private readonly IParsingService _parsingService;
private readonly IConfigService _configService;
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;
_parsingService = parsingService;
_configService = configService;
_logger = logger;
}
@@ -68,30 +71,50 @@ namespace NzbDrone.Core.DecisionEngine
{
var parsedMovieInfo = Parser.Parser.ParseMovieTitle(report.Title);
if (parsedMovieInfo != null && !parsedMovieInfo.MovieTitle.IsNullOrWhiteSpace())
{
RemoteMovie remoteMovie = _parsingService.Map(parsedMovieInfo, report.ImdbId.ToString(), searchCriteria);
remoteMovie.Release = report;
if (parsedMovieInfo != null && !parsedMovieInfo.MovieTitle.IsNullOrWhiteSpace())
{
RemoteMovie remoteMovie = _parsingService.Map(parsedMovieInfo, report.ImdbId.ToString(), searchCriteria);
remoteMovie.Release = report;
if (remoteMovie.Movie == null)
{
decision = new DownloadDecision(remoteMovie, new Rejection("Unknown movie. Cannot parse release name."));
}
else
{
if (parsedMovieInfo.Quality.HardcodedSubs.IsNotNullOrWhiteSpace())
{
remoteMovie.DownloadAllowed = true;
decision = new DownloadDecision(remoteMovie, new Rejection("Hardcoded subs found: " + parsedMovieInfo.Quality.HardcodedSubs));
}
else
{
remoteMovie.DownloadAllowed = true;
decision = GetDecisionForReport(remoteMovie, searchCriteria);
}
}
}
if (remoteMovie.Movie == null)
{
decision = new DownloadDecision(remoteMovie, new Rejection("Unknown movie. Movie found does not match wanted movie."));
}
else
{
if (parsedMovieInfo.Quality.HardcodedSubs.IsNotNullOrWhiteSpace())
{
remoteMovie.DownloadAllowed = true;
if (_configService.AllowHardcodedSubs)
{
decision = GetDecisionForReport(remoteMovie, searchCriteria);
}
else
{
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)
{

View File

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

View File

@@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.IndexerSearch.Definitions;
@@ -121,6 +121,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
}
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)
{
var minSize = qualityDefinition.MinSize.Value.Megabytes();

View File

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

View File

@@ -1,13 +1,19 @@
namespace NzbDrone.Core.Download.Clients.DownloadStation
{
public class DiskStationApiInfo
{
{
private string _path;
public int MaxVersion { 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
{
get { return _path; }

View File

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

View File

@@ -1,63 +1,77 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Download.Clients.DownloadStation.Responses;
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;
protected readonly Logger _logger;
private bool _authenticated;
private readonly ICached<DiskStationApiInfo> _infoCache;
private readonly ICached<string> _sessionCache;
private readonly DiskStationApi _apiType;
private readonly string _apiName;
private static readonly DiskStationApiInfo _apiInfo;
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;
_logger = logger;
_infoCache = cacheManager.GetCache<DiskStationApiInfo>(typeof(DiskStationProxyBase), "apiInfo");
_sessionCache = cacheManager.GetCache<string>(typeof(DiskStationProxyBase), "sessions");
_apiType = apiType;
_apiName = apiName;
}
protected DiskStationResponse<object> ProcessRequest(DiskStationApi api,
Dictionary<string, object> arguments,
DownloadStationSettings settings,
string operation,
HttpMethod method = HttpMethod.GET)
private string GenerateSessionCacheKey(DownloadStationSettings settings)
{
return ProcessRequest<object>(api, arguments, settings, operation, method);
return $"{settings.Username}@{settings.Host}:{settings.Port}";
}
protected DiskStationResponse<T> ProcessRequest<T>(DiskStationApi api,
Dictionary<string, object> arguments,
DownloadStationSettings settings,
string operation,
HttpMethod method = HttpMethod.GET,
int retries = 0) where T : new()
protected DiskStationResponse<T> ProcessRequest<T>(HttpRequestBuilder requestBuilder,
string operation,
DownloadStationSettings settings) where T : new()
{
if (retries == 5)
{
throw new DownloadClientException("Try to process request to {0} with {1} more than 5 times", api, arguments.ToJson().ToString());
}
return ProcessRequest<T>(requestBuilder, operation, _apiType, settings);
}
if (!_authenticated && api != DiskStationApi.Info && api != DiskStationApi.DSMInfo)
{
AuthenticateClient(settings);
}
var request = BuildRequest(settings, api, arguments, method);
private DiskStationResponse<T> ProcessRequest<T>(HttpRequestBuilder requestBuilder,
string operation,
DiskStationApi api,
DownloadStationSettings settings) where T : new()
{
var request = requestBuilder.Build();
var response = _httpClient.Execute(request);
_logger.Debug("Trying to {0}", operation);
@@ -77,16 +91,14 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
if (responseContent.Error.SessionError)
{
_authenticated = false;
_sessionCache.Remove(GenerateSessionCacheKey(settings));
if (responseContent.Error.Code == 105)
{
throw new DownloadClientAuthenticationException(msg);
}
return ProcessRequest<T>(api, arguments, settings, operation, method, ++retries);
}
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>
{
{ "api", "SYNO.API.Auth" },
{ "version", "1" },
{ "method", "login" },
{ "account", settings.Username },
{ "passwd", settings.Password },
{ "format", "cookie" },
{ "session", "DownloadStation" },
};
var authInfo = GetApiInfo(DiskStationApi.Auth, settings);
var authLoginRequest = BuildRequest(settings, DiskStationApi.Auth, arguments, HttpMethod.GET);
authLoginRequest.StoreResponseCookie = true;
var requestBuilder = BuildRequest(settings, authInfo, "login", 2);
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);
var authResponse = Json.Deserialize<DiskStationResponse<DiskStationAuthResponse>>(response.Content);
_authenticated = authResponse.Success;
if (!_authenticated)
{
throw new DownloadClientAuthenticationException(downloadStationResponse.Error.GetMessage(DiskStationApi.Auth));
}
return authResponse.Data.SId;
}
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))
{
GetApiVersion(settings, api);
}
var info = GetApiInfo(_apiType, settings);
var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port).Resource($"webapi/{Resources[api]}");
requestBuilder.Method = method;
return BuildRequest(settings, info, methodName, apiVersion, httpVerb);
}
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.SuppressHttpError = true;
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"))
{
requestBuilder.Headers.ContentType = "multipart/form-data";
throw new ArgumentOutOfRangeException(nameof(apiVersion));
}
foreach (var arg in arguments)
{
if (arg.Key == "file")
{
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
if (httpVerb == HttpMethod.POST)
{
if (apiInfo.NeedsAuthentication)
{
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
{
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>
{
{ "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");
}
return GetApiInfo(_apiType, settings);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,6 @@ using System.Linq;
using System.Net;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
@@ -20,7 +19,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{
public class TorrentDownloadStation : TorrentClientBase<DownloadStationSettings>
{
protected readonly IDownloadStationProxy _proxy;
protected readonly IDownloadStationInfoProxy _dsInfoProxy;
protected readonly IDownloadStationTaskProxy _dsTaskProxy;
protected readonly ISharedFolderResolver _sharedFolderResolver;
protected readonly ISerialNumberProvider _serialNumberProvider;
protected readonly IFileStationProxy _fileStationProxy;
@@ -28,7 +28,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
public TorrentDownloadStation(ISharedFolderResolver sharedFolderResolver,
ISerialNumberProvider serialNumberProvider,
IFileStationProxy fileStationProxy,
IDownloadStationProxy proxy,
IDownloadStationInfoProxy dsInfoProxy,
IDownloadStationTaskProxy dsTaskProxy,
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
@@ -37,7 +38,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger)
{
_proxy = proxy;
_dsInfoProxy = dsInfoProxy;
_dsTaskProxy = dsTaskProxy;
_fileStationProxy = fileStationProxy;
_sharedFolderResolver = sharedFolderResolver;
_serialNumberProvider = serialNumberProvider;
@@ -47,7 +49,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
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()
@@ -129,7 +131,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
DeleteItemData(downloadId);
}
_proxy.RemoveTask(ParseDownloadId(downloadId), Settings);
_dsTaskProxy.RemoveTask(ParseDownloadId(downloadId), Settings);
_logger.Debug("{0} removed correctly", downloadId);
}
@@ -158,7 +160,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{
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);
@@ -177,7 +179,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{
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));
@@ -368,13 +370,13 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
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;
@@ -405,7 +407,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
protected string GetDefaultDir()
{
var config = _proxy.GetConfig(Settings);
var config = _dsInfoProxy.GetConfig(Settings);
var path = config["default_destination"] as string;

View File

@@ -17,7 +17,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{
public class UsenetDownloadStation : UsenetClientBase<DownloadStationSettings>
{
protected readonly IDownloadStationProxy _proxy;
protected readonly IDownloadStationInfoProxy _dsInfoProxy;
protected readonly IDownloadStationTaskProxy _dsTaskProxy;
protected readonly ISharedFolderResolver _sharedFolderResolver;
protected readonly ISerialNumberProvider _serialNumberProvider;
protected readonly IFileStationProxy _fileStationProxy;
@@ -25,7 +26,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
public UsenetDownloadStation(ISharedFolderResolver sharedFolderResolver,
ISerialNumberProvider serialNumberProvider,
IFileStationProxy fileStationProxy,
IDownloadStationProxy proxy,
IDownloadStationInfoProxy dsInfoProxy,
IDownloadStationTaskProxy dsTaskProxy,
IHttpClient httpClient,
IConfigService configService,
IDiskProvider diskProvider,
@@ -34,7 +36,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
)
: base(httpClient, configService, diskProvider, remotePathMappingService, logger)
{
_proxy = proxy;
_dsInfoProxy = dsInfoProxy;
_dsTaskProxy = dsTaskProxy;
_fileStationProxy = fileStationProxy;
_sharedFolderResolver = sharedFolderResolver;
_serialNumberProvider = serialNumberProvider;
@@ -44,7 +47,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
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()
@@ -153,7 +156,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
DeleteItemData(downloadId);
}
_proxy.RemoveTask(ParseDownloadId(downloadId), Settings);
_dsTaskProxy.RemoveTask(ParseDownloadId(downloadId), Settings);
_logger.Debug("{0} removed correctly", downloadId);
}
@@ -166,7 +169,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{
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);
@@ -281,13 +284,13 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
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;
@@ -399,7 +402,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
protected string GetDefaultDir()
{
var config = _proxy.GetConfig(Settings);
var config = _dsInfoProxy.GetConfig(Settings);
var path = config["default_destination"] as string;

View File

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

View File

@@ -225,10 +225,18 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
if (!completeDir.IsRooted)
{
var queue = _proxy.GetQueue(0, 1, Settings);
var defaultRootFolder = new OsPath(queue.DefaultRootFolder);
if (HasVersion(2, 0))
{
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)
@@ -448,50 +456,47 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
};
}
}
if (config.Misc.enable_tv_sorting)
if (config.Misc.enable_tv_sorting && ContainsCategory(config.Misc.tv_categories, Settings.TvCategory))
{
if (!config.Misc.tv_categories.Any<string>() ||
config.Misc.tv_categories.Contains(Settings.TvCategory) ||
(Settings.TvCategory.IsNullOrWhiteSpace() && config.Misc.tv_categories.Contains("Default")))
return new NzbDroneValidationFailure("TvCategory", "Disable TV Sorting")
{
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)
if (config.Misc.enable_movie_sorting && ContainsCategory(config.Misc.movie_categories, Settings.TvCategory))
{
if (!config.Misc.movie_categories.Any<string>() ||
config.Misc.movie_categories.Contains(Settings.TvCategory) ||
(Settings.TvCategory.IsNullOrWhiteSpace() && config.Misc.movie_categories.Contains("Default")))
return new NzbDroneValidationFailure("TvCategory", "Disable Movie Sorting")
{
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)
if (config.Misc.enable_date_sorting && ContainsCategory(config.Misc.date_categories, Settings.TvCategory))
{
if (!config.Misc.date_categories.Any<string>() ||
config.Misc.date_categories.Contains(Settings.TvCategory) ||
(Settings.TvCategory.IsNullOrWhiteSpace() && config.Misc.date_categories.Contains("Default")))
return new NzbDroneValidationFailure("TvCategory", "Disable Date Sorting")
{
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;
}
private bool ContainsCategory(IEnumerable<string> categories, string category)
{
if (categories == null || categories.Empty())
{
return true;
}
if (category.IsNullOrWhiteSpace())
{
category = "Default";
}
return categories.Contains(category);
}
}
}

View File

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

View File

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

View File

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

View File

@@ -49,52 +49,36 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
{
_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 retryDelay = 500;
if (WaitForTorrent(hash, tries, retryDelay))
{
_logger.Info("Resolved magnet for {0}", remoteMovie.Movie.CleanTitle);
SetDownloadDirectory(hash);
_proxy.SetTorrentLabel(hash, Settings.MovieCategory, Settings);
_proxy.StartTorrent(hash, Settings);
return hash;
}
else
// Wait a bit for the magnet to be resolved.
if (!WaitForTorrent(hash, tries, retryDelay))
{
_logger.Warn("rTorrent could not resolve magnet within {0} seconds, download may remain stuck: {1}.", tries * retryDelay / 1000, magnetLink);
return hash;
}
return hash;
}
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 retryDelay = 200;
if (WaitForTorrent(hash, tries, retryDelay))
var tries = 10;
var retryDelay = 500;
if (!WaitForTorrent(hash, tries, retryDelay))
{
_proxy.SetTorrentLabel(hash, Settings.MovieCategory, Settings);
SetDownloadDirectory(hash);
_proxy.StartTorrent(hash, Settings);
return hash;
}
else
{
_logger.Debug("rTorrent could not add file");
_logger.Debug("rTorrent didn't add the torrent within {0} seconds: {1}.", tries * retryDelay / 1000, filename);
RemoveItem(hash, true);
throw new ReleaseDownloadException(remoteMovie.Release, "Downloading torrent failed");
}
return hash;
}
public override string Name => "rTorrent";
@@ -233,14 +217,6 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
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)
{
for (var i = 0; i < tries; i++)

View File

@@ -13,15 +13,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
string GetVersion(RTorrentSettings settings);
List<RTorrentTorrent> GetTorrents(RTorrentSettings settings);
void AddTorrentFromUrl(string torrentUrl, RTorrentSettings settings);
void AddTorrentFromFile(string fileName, byte[] fileContent, RTorrentSettings settings);
void AddTorrentFromUrl(string torrentUrl, string label, RTorrentPriority priority, string directory, RTorrentSettings settings);
void AddTorrentFromFile(string fileName, byte[] fileContent, string label, RTorrentPriority priority, string directory, 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);
void StartTorrent(string hash, RTorrentSettings settings);
void SetDeferredMagnetProperties(string hash, string category, string directory, RTorrentPriority priority, RTorrentSettings settings);
}
public interface IRTorrent : IXmlRpcProxy
@@ -29,35 +24,20 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
[XmlRpcMethod("d.multicall2")]
object[] TorrentMulticall(params string[] parameters);
[XmlRpcMethod("load.normal")]
int LoadUrl(string target, string data);
[XmlRpcMethod("load.start")]
int LoadStart(string target, string data, params string[] commands);
[XmlRpcMethod("load.raw")]
int LoadBinary(string target, byte[] data);
[XmlRpcMethod("load.raw_start")]
int LoadRawStart(string target, byte[] data, params string[] commands);
[XmlRpcMethod("d.erase")]
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")]
string GetName(string hash);
[XmlRpcMethod("system.client_version")]
string GetVersion();
[XmlRpcMethod("system.multicall")]
object[] SystemMulticall(object[] parameters);
}
public class RTorrentProxy : IRTorrentProxy
@@ -101,20 +81,20 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
var items = new List<RTorrentTorrent>();
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();
item.Name = (string) torrent[0];
item.Hash = (string) torrent[1];
item.Path = (string) torrent[2];
item.Name = (string)torrent[0];
item.Hash = (string)torrent[1];
item.Path = (string)torrent[2];
item.Category = labelDecoded;
item.TotalSize = (long) torrent[4];
item.RemainingSize = (long) torrent[5];
item.DownRate = (long) torrent[6];
item.Ratio = (long) torrent[7];
item.IsOpen = Convert.ToBoolean((long) torrent[8]);
item.IsActive = Convert.ToBoolean((long) torrent[9]);
item.IsFinished = Convert.ToBoolean((long) torrent[10]);
item.TotalSize = (long)torrent[4];
item.RemainingSize = (long)torrent[5];
item.DownRate = (long)torrent[6];
item.Ratio = (long)torrent[7];
item.IsOpen = Convert.ToBoolean((long)torrent[8]);
item.IsActive = Convert.ToBoolean((long)torrent[9]);
item.IsFinished = Convert.ToBoolean((long)torrent[10]);
items.Add(item);
}
@@ -122,26 +102,26 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
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");
var client = BuildClient(settings);
var response = client.LoadUrl("", torrentUrl);
var response = client.LoadStart("", torrentUrl, GetCommands(label, priority, directory));
if (response != 0)
{
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");
var client = BuildClient(settings);
var response = client.LoadBinary("", fileContent);
var response = client.LoadRawStart("", fileContent, GetCommands(label, priority, directory));
if (response != 0)
{
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);
var response = client.SetPriority(hash, (long) priority);
if (response != 0)
if (label.IsNotNullOrWhiteSpace())
{
throw new DownloadClientException("Could not set priority on torrent: {0}.", hash);
}
}
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);
result.Add("d.custom1.set=" + label);
}
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=
commands.Add("d.open=");
commands.Add("d.start=");
if (commands.Any())
if (directory.IsNotNullOrWhiteSpace())
{
var key = "event.download.inserted_new";
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);
}
result.Add("d.directory.set=" + directory);
}
return result.ToArray();
}
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)
{
var client = XmlRpcProxyGen.Create<IRTorrent>();
@@ -316,4 +202,4 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
return client;
}
}
}
}

View File

@@ -129,7 +129,14 @@ namespace NzbDrone.Core.Download
{
var statusMessages = importResults
.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();
trackedDownload.Warn(statusMessages);

View File

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

View File

@@ -117,45 +117,43 @@ namespace NzbDrone.Core.Download.Pending
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)
{
ect = nextRssSync.Value;
}
else
{
ect = ect.AddMinutes(_configService.RssSyncInterval);
}
if (ect < nextRssSync.Value)
{
ect = nextRssSync.Value;
}
else
{
ect = ect.AddMinutes(_configService.RssSyncInterval);
}
var queue = new Queue.Queue
{
Id = GetQueueId(pendingRelease, pendingRelease.RemoteMovie.Movie),
Series = null,
Episode = null,
Movie = pendingRelease.RemoteMovie.Movie,
Quality = pendingRelease.RemoteMovie.ParsedMovieInfo.Quality,
Title = pendingRelease.Title,
Size = pendingRelease.RemoteMovie.Release.Size,
Sizeleft = pendingRelease.RemoteMovie.Release.Size,
RemoteMovie = pendingRelease.RemoteMovie,
Timeleft = ect.Subtract(DateTime.UtcNow),
EstimatedCompletionTime = ect,
Status = "Pending",
Protocol = pendingRelease.RemoteMovie.Release.DownloadProtocol
};
queued.Add(queue);
//}
var queue = new Queue.Queue
{
Id = GetQueueId(pendingRelease, pendingRelease.RemoteMovie.Movie),
Series = null,
Episode = null,
Movie = pendingRelease.RemoteMovie.Movie,
Quality = pendingRelease.RemoteMovie.ParsedMovieInfo?.Quality ?? new QualityModel(),
Title = pendingRelease.Title,
Size = pendingRelease.RemoteMovie.Release.Size,
Sizeleft = pendingRelease.RemoteMovie.Release.Size,
RemoteMovie = pendingRelease.RemoteMovie,
Timeleft = ect.Subtract(DateTime.UtcNow),
EstimatedCompletionTime = ect,
Status = "Pending",
Protocol = pendingRelease.RemoteMovie.Release.DownloadProtocol
};
queued.Add(queue);
}
//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))
.First();
});
@@ -220,14 +218,20 @@ namespace NzbDrone.Core.Download.Pending
private void Insert(DownloadDecision decision)
{
_repository.Insert(new PendingRelease
{
MovieId = decision.RemoteMovie.Movie.Id,
ParsedMovieInfo = decision.RemoteMovie.ParsedMovieInfo,
Release = decision.RemoteMovie.Release,
Title = decision.RemoteMovie.Release.Title,
Added = DateTime.UtcNow
});
var release = new PendingRelease
{
MovieId = decision.RemoteMovie.Movie.Id,
ParsedMovieInfo = decision.RemoteMovie.ParsedMovieInfo,
Release = decision.RemoteMovie.Release,
Title = decision.RemoteMovie.Release.Title,
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());
}
@@ -254,12 +258,12 @@ namespace NzbDrone.Core.Download.Pending
return new[] { delay, minimumAge }.Max();
}
private void RemoveGrabbed(RemoteMovie remoteEpisode)
private void RemoveGrabbed(RemoteMovie remoteMovie)
{
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();
if (existingReports.Empty())
@@ -267,11 +271,11 @@ namespace NzbDrone.Core.Download.Pending
return;
}
var profile = remoteEpisode.Movie.Profile.Value;
var profile = remoteMovie.Movie.Profile.Value;
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);
//Only remove lower/equal quality pending releases

View File

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

View File

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

View File

@@ -6,21 +6,21 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
{
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()
{
var series = _seriesRepository.All().ToList();
var movies = _movieRepository.All().ToList();
series.ForEach(s =>
movies.ForEach(m =>
{
s.CleanTitle = s.CleanTitle.CleanSeriesTitle();
_seriesRepository.Update(s);
m.CleanTitle = m.CleanTitle.CleanSeriesTitle();
_movieRepository.Update(m);
});
}
}

View File

@@ -1,7 +1,5 @@
using System.Collections.Generic;
using System.Net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser.Model;
@@ -64,12 +62,19 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
Subtitles = x.Element("subtitles").Value,
EncodeStatus = x.Element("encodestatus").Value,
Freeleech = x.Element("freeleech").Value,
ImdbId = x.Element("imdb").Value
}).ToList();
foreach (var torrent in torrents)
{
var id = torrent.Id;
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()
{
@@ -80,7 +85,9 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
InfoUrl = GetInfoUrl(torrent.GroupId, id),
Seeders = int.Parse(torrent.Seeders),
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
PublishDate = torrent.Time.ToUniversalTime()
PublishDate = torrent.Time.ToUniversalTime(),
ImdbId = int.Parse(torrent.ImdbId.Substring(2)),
IndexerFlags = flags,
});
}
}

View File

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

View File

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

View File

@@ -53,6 +53,18 @@ namespace NzbDrone.Core.Indexers.HDBits
var id = result.Id;
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()
{
Guid = string.Format("HDBits-{0}", id),
@@ -64,7 +76,9 @@ namespace NzbDrone.Core.Indexers.HDBits
Seeders = result.Seeders,
Peers = result.Leechers + result.Seeders,
PublishDate = result.Added.ToUniversalTime(),
Internal = internalRelease
Internal = internalRelease,
ImdbId = result.ImdbInfo?.Id ?? 0,
IndexerFlags = flags
});
}

View File

@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Xml;
using System.Xml.Linq;
using NLog;
using NzbDrone.Common.Cache;
@@ -49,15 +51,30 @@ namespace NzbDrone.Core.Indexers.Newznab
var request = new HttpRequest(url, HttpAccept.Rss);
HttpResponse response;
try
{
var response = _httpClient.Get(request);
capabilities = ParseCapabilities(response);
response = _httpClient.Get(request);
}
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;

View File

@@ -52,7 +52,7 @@ namespace NzbDrone.Core.Indexers.Newznab
{
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)}"));
}

View File

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

View File

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

View File

@@ -46,6 +46,9 @@ namespace NzbDrone.Core.Indexers.Torznab
torrentInfo.ImdbId = int.Parse(GetImdbId(item).Substring(2));
}
}
torrentInfo.IndexerFlags = GetFlags(item);
return torrentInfo;
}
@@ -151,6 +154,32 @@ namespace NzbDrone.Core.Indexers.Torznab
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 = "")
{
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 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;
}
}

View File

@@ -44,6 +44,7 @@ namespace NzbDrone.Core.MediaFiles
private readonly IMediaFileTableCleanupService _mediaFileTableCleanupService;
private readonly IEventAggregator _eventAggregator;
private readonly IMovieService _movieService;
private readonly IMovieFileRepository _movieFileRepository;
private readonly Logger _logger;
public DiskScanService(IDiskProvider diskProvider,
@@ -55,6 +56,7 @@ namespace NzbDrone.Core.MediaFiles
IMediaFileTableCleanupService mediaFileTableCleanupService,
IEventAggregator eventAggregator,
IMovieService movieService,
IMovieFileRepository movieFileRepository,
Logger logger)
{
_diskProvider = diskProvider;
@@ -66,6 +68,7 @@ namespace NzbDrone.Core.MediaFiles
_mediaFileTableCleanupService = mediaFileTableCleanupService;
_eventAggregator = eventAggregator;
_movieService = movieService;
_movieFileRepository = movieFileRepository;
_logger = logger;
}
@@ -136,14 +139,14 @@ namespace NzbDrone.Core.MediaFiles
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));
return;
}
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));
return;
}
@@ -155,13 +158,20 @@ namespace NzbDrone.Core.MediaFiles
if (_configService.CreateEmptySeriesFolders &&
_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);
SetPermissions(movie.Path);
}
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));

View File

@@ -135,16 +135,16 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
localMovie.Size = _diskProvider.GetFileSize(file);
_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
if (sceneSource)
if (sceneSource && ShouldCheckQualityForParsedQuality(current.Quality))
{
localMovie.MediaInfo = _videoFileInfoReader.GetMediaInfo(file);
if (shouldCheckQuality)
{
_logger.Debug("Checking quality for this video file to make sure nothing mismatched.");
var width = localMovie.MediaInfo.Width;
var current = localMovie.Quality;
var qualityName = current.Quality.Name.ToLower();
QualityModel updated = null;
if (width > 2000)
@@ -565,5 +565,20 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
return false;
}
private bool ShouldCheckQualityForParsedQuality(Quality quality)
{
List<Quality> shouldNotCheck = new List<Quality> { Quality.WORKPRINT, Quality.TELECINE, Quality.TELESYNC,
Quality.DVDSCR, Quality.DVD, Quality.CAM, Quality.DVDR, Quality.Remux1080p, Quality.Remux2160p, Quality.REGIONAL
};
if (shouldNotCheck.Contains(quality))
{
return false;
}
return true;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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