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

Compare commits

..

72 Commits

Author SHA1 Message Date
Leonardo Galli 3c8162f1d6 Fix test. 2017-05-12 16:52:38 +02:00
Leonardo Galli 096d24ed91 Merge branch 'feature/better-mapping-info' into develop 2017-05-12 16:09:35 +02:00
Leonardo Galli 63e3361fb5 Movies with same name but different year being downloaded regardlessly is now fixed! 2017-05-12 16:09:23 +02:00
Leonardo Galli 51854ef73b Update TaskManager.cs 2017-05-11 23:05:12 +02:00
Levi Wilcox a0486b54a6 Fixed design issue when deleting css bug (#1480) Fixes #1475 2017-05-11 22:05:34 +02:00
Leonardo Galli 7ed0db10cb 10 Movies are now shown on discover as well as search results.
Show more should also be more consistent now.
2017-05-09 22:28:15 +02:00
Leonardo Galli e94591a290 Hotfix for when ignored movies would appear again after clicking on show more. 2017-05-09 21:28:22 +02:00
Leonardo Galli fccd02a0ca Merge remote-tracking branch 'origin/develop' into develop 2017-05-09 20:44:43 +02:00
Leonardo Galli b49f0e70ed Merge branch 'feature/better-import-exclusions' into develop 2017-05-09 20:44:17 +02:00
Leonardo Galli fc1585e900 Completely overhauled how import exclusions work.
Currently new exclusions can only be added when adding new movies or deleting old ones. Not manually in the settings menu.
Movies can now be hidden in the new discover feature by using the new import exclusions!
2017-05-09 20:44:07 +02:00
Leonardo Galli b36ac091fc Fix appveyor build 2017-05-09 19:49:30 +02:00
Leonardo Galli ab28bfead2 Merge branch 'develop' into feature/better-import-exclusions 2017-05-09 16:55:19 +02:00
Leonardo Galli 3ab3fbfd57 Added ability to discover new movies based on upcoming blurays as well as popular movies (borrowed from steven lu :)) 2017-05-09 16:46:19 +02:00
Leonardo Galli d133ee3143 Hopefully more logging to catch errors better. 2017-05-08 18:46:03 +02:00
Leonardo Galli 98b6932ffe Merge remote-tracking branch 'origin/develop' into develop 2017-05-08 18:07:38 +02:00
Leonardo Galli 58e81a916c Fixed error when language is present in title, but has dots instead of spaces. For example The.Danish.Girl.2015 2017-05-06 15:26:35 +02:00
PatrickGHanna 817f48448c Fix: A small bug fix for items loading as undefined in organize modal. Movie titles should now show up correctly. (#1424) 2017-05-06 12:31:41 +02:00
hotio 1eca179b4e Update Kodi icon, fixes #1464 (#1492) 2017-05-06 12:30:36 +02:00
Leonardo Galli b05d505bce Fixed Final in titles parsing as an edition. 2017-05-02 22:15:03 +02:00
Leonardo Galli ace426e69f Added initial migration. 2017-04-30 14:05:41 +02:00
Mitchell Cash a57c9917cc Fixed: Radarr not importing torrents in Vuze if the torrent already finished seeding and was stopped (#1471) 2017-04-30 13:14:49 +02:00
Mitchell Cash 9e84b4a782 Fixed: Incorrect imports with Vuze when torrent contains a single file. (#1470) 2017-04-30 10:37:51 +02:00
Mitchell Cash 494ef16735 Fixed: Sonarr UI Authentication cookie should be placed on path (UrlBase) instead of domain alone. Fixes ##1451 2017-04-30 10:30:59 +02:00
Leonardo Galli 5a0f02007f Added trailer links to the discovery page. 2017-04-28 14:14:02 +02:00
Leonardo Galli b1025e7229 Use Post for tmdbids request, to avoid too long URIs. 2017-04-28 13:14:00 +02:00
Leonardo Galli 446d661345 Added discovery tab based on tmdb recommendations based on your existing movies. (#1450)
Keep scroll position on more in search result view.
Added TMDB score to search results.
2017-04-28 11:04:30 +02:00
David Pooley 3eb351823e Tidy up layout of buttons on the Add Movies page for mobile/tablet (#1454) 2017-04-28 10:59:55 +02:00
morberg 835a7cffa1 Rename Sonarr to Radarr for OSX App 2017-04-26 18:44:57 +02:00
Leonardo Galli e728330ce4 Minor text fixes. 2017-04-26 15:45:06 +02:00
Leonardo Galli 6f3118c142 Merge remote-tracking branch 'origin/develop' into develop 2017-04-26 15:17:49 +02:00
Leonardo Galli b568072140 Change default page size to 250. Should help with safari timeouts. 2017-04-26 15:17:42 +02:00
Leonardo Galli 7db92c6bcf Enable automatic renaming, according to naming scheme, of movie folder after creation of the movie. (#1349)
Please test everything you can about this and report back if everything still works correctly.
2017-04-26 13:31:55 +02:00
Leonardo Galli f1e8a9acfc Fix for error when clicking Rescan Drone Folder 2017-04-19 22:04:34 +02:00
Leonardo Galli dae389ce64 Fix for error when trying to manually import. 2017-04-19 22:03:52 +02:00
Leonardo Galli 7c3d8c8ff9 Added multiple new editions such as FanEdit, Anniversary and 2in1. 2017-04-19 21:02:01 +02:00
Donald Webster 8ca66fb61a Change smtp.google.com to smtp.gmail.com (#1410) 2017-04-18 19:28:15 +02:00
Leonardo Galli 064844ac0c Fix PTP_Approved turning into HDBits Internal. 2017-04-18 16:41:40 +02:00
Leonardo Galli 5540594ecf Fix ptp tests. 2017-04-17 22:30:51 +02:00
Leonardo Galli 433ae019de AHD, PTP and HDB support the new indexer flags too now! Indexer flags can be preferred over other releases. 2017-04-17 17:12:09 +02:00
Leonardo Galli 10091b9454 Movies with Umlauts are now correctly matched and have correct CleanTitles.
An update library is recommended. Fixes #1338
2017-04-17 13:08:47 +02:00
Leonardo Galli 475851775f Minor Text fixes. 2017-04-17 12:11:35 +02:00
Leonardo Galli 0fff862fd2 Updated debug movie title to include Year. 2017-04-17 12:09:24 +02:00
Leonardo Galli 23754c49dc Fix error when MinimumAvailability was Announced and Delay was negative. 2017-04-17 12:08:03 +02:00
Leonardo Galli fbf790e9fd Disable PreDB sync for now. 2017-04-17 12:04:54 +02:00
Leonardo Galli 6d00bd0f7a Stats are now sent to our server instead of Sonarr's :) 2017-04-17 12:01:56 +02:00
hotio 5bf95e0d9e Update Series reference to Movies, should fix #1399 (#1402) 2017-04-16 23:20:42 +02:00
Leonardo Galli 8bb4b02be7 Fix for sql error. Did not think everything through exactly.
Fixes #1390.
2017-04-16 00:48:39 +02:00
Leonardo Galli b26a036eed Fix when MovieTitle is the empty string (should not occur, but what evs)
Fixes #1389
2017-04-16 00:26:16 +02:00
Leonardo Galli ef57882291 Fixes Movie Size not showing correctly.
Fixes #1379
2017-04-15 15:47:40 +02:00
Leonardo Galli 32a2407ad1 Fixed an issue where movies which were labelled with an alternative title could not be found.
Fixes #557 Fixes #1387 Probably fixes #1372, probably fixes #555
2017-04-15 14:50:34 +02:00
Leonardo Galli 33b48eec95 Indexer flags implementation. (#1377) Will be further finalized over the next few weeks with Freelech, preferring of certain flags, etc 2017-04-14 22:27:48 +02:00
Leonardo Galli 3790dc9109 Added test for fix in last commit. 2017-04-13 14:18:48 +02:00
Leonardo Galli 343d849536 Add default runtime when runtime info of tmdb says 0.
Fixes #1371
2017-04-13 14:04:38 +02:00
Leonardo Galli c36b259fa9 Fixes an issue where the semicolon and space afterwards was replaced.
This caused issues with cleaning the title afterwards. Fixes #1185
2017-04-13 10:27:00 +02:00
Leonardo Galli 6463913f22 Final tweak for package.sh 2017-04-12 22:31:49 +02:00
Leonardo Galli e39deb4bdc Update branch. 2017-04-12 21:57:59 +02:00
Leonardo Galli 9ca2c21547 This should finally fix all packaging stuff. 2017-04-12 21:46:33 +02:00
Leonardo Galli f376360611 Update packages.sh some more. 2017-04-12 21:23:02 +02:00
Leonardo Galli 3e966d4d58 Update package.sh script 2017-04-12 21:20:33 +02:00
Leonardo Galli e036267c33 Test fixes. 2017-04-12 18:23:04 +02:00
Leonardo Galli 7d4378ca7a More test debugging. 2017-04-12 18:12:47 +02:00
Leonardo Galli ee1ebfd893 Remote Test debugging yey! 2017-04-12 17:58:52 +02:00
Leonardo Galli 403fd0f0c0 Remove unecessary test. 2017-04-12 17:17:22 +02:00
Leonardo Galli fc5ac8219f Using NUnit.Runners so that teamcity build works. 2017-04-12 17:03:53 +02:00
Leonardo Galli 600a433faa Merge remote-tracking branch 'origin/develop' into develop 2017-04-12 16:45:23 +02:00
Leonardo Galli 5609facd9d Fixed package script for Teamcity. 2017-04-12 16:45:13 +02:00
Leonardo Galli 36ea6c6b99 Turn installer back on. 2017-04-12 12:23:07 +02:00
Leonardo Galli 56ac87c760 Disabled installer being picked up, causes error with update api. 2017-04-11 21:19:31 +02:00
Leonardo Galli 47753c47a5 Now artifacts get pushed even if tests fail 2017-04-11 19:40:13 +02:00
Leonardo Galli 712c0eb84a Log if ParsedMovieInfo is NULL. 2017-04-11 19:31:33 +02:00
Leonardo Galli 8765155223 Merge remote-tracking branch 'origin/develop' into develop 2017-04-11 12:16:14 +02:00
Leonardo Galli 63d7596e98 Catching predb.me errors hopefully. 2017-04-11 12:15:52 +02:00
128 changed files with 2227 additions and 388 deletions
+5
View File
@@ -37,6 +37,11 @@ cache:
pull_requests: pull_requests:
do_not_increment_build_number: true do_not_increment_build_number: true
on_failure:
- ps: Get-ChildItem .\_artifacts\*.zip | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
- ps: Get-ChildItem .\_artifacts\*.exe | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
- ps: Get-ChildItem .\_artifacts\*.tar.gz | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
only_commits: only_commits:
files: files:
- src/ - src/
+8 -6
View File
@@ -104,11 +104,13 @@ Task("Compile").Does(() => {
}); });
Task("Gulp").Does(() => { Task("Gulp").Does(() => {
Npm NpmInstall(new NpmInstallSettings {
.WithLogLevel(NpmLogLevel.Silent) LogLevel = NpmLogLevel.Silent,
.FromPath(".") WorkingDirectory = "./",
.Install() Production = true
.RunScript("build"); });
NpmRunScript("build");
}); });
Task("PackageMono").Does(() => { Task("PackageMono").Does(() => {
@@ -167,7 +169,7 @@ Task("PackageOsx").Does(() => {
CopyFiles(sourceFolder + "/Libraries/MediaInfo/*.dylib", outputFolderOsx); CopyFiles(sourceFolder + "/Libraries/MediaInfo/*.dylib", outputFolderOsx);
// Adding Startup script // Adding Startup script
CopyFile("./osx/Sonarr", outputFolderOsx + "/Sonarr"); CopyFile("./osx/Radarr", outputFolderOsx + "/Radarr");
}); });
Task("PackageOsxApp").Does(() => { Task("PackageOsxApp").Does(() => {
+3 -3
View File
@@ -181,7 +181,7 @@ PackageOsx()
cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderOsx cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderOsx
echo "Adding Startup script" echo "Adding Startup script"
cp ./osx/Sonarr $outputFolderOsx cp ./osx/Radarr $outputFolderOsx
echo "##teamcity[progressFinish 'Creating OS X Package']" echo "##teamcity[progressFinish 'Creating OS X Package']"
} }
@@ -208,9 +208,9 @@ PackageTests()
find $sourceFolder -path $testSearchPattern -exec cp -r -u -T "{}" $testPackageFolder \; find $sourceFolder -path $testSearchPattern -exec cp -r -u -T "{}" $testPackageFolder \;
if [ $runtime = "dotnet" ] ; then if [ $runtime = "dotnet" ] ; then
$nuget install NUnit.ConsoleRunner -Version 3.2.0 -Output $testPackageFolder $nuget install NUnit.Runners -Version 3.2.1 -Output $testPackageFolder
else else
mono $nuget install NUnit.ConsoleRunner -Version 3.2.0 -Output $testPackageFolder mono $nuget install NUnit.Runners -Version 3.2.1 -Output $testPackageFolder
fi fi
cp $outputFolder/*.dll $testPackageFolder cp $outputFolder/*.dll $testPackageFolder
+44
View File
@@ -0,0 +1,44 @@
input1 = """Prometheus.Special.Edition.Fan Edit.2012..BRRip.x264.AAC-m2g
Star Wars Episode IV - A New Hope (Despecialized) 1999.mkv
Prometheus.(Special.Edition.Remastered).2012.[Bluray-1080p].mkv
Prometheus Extended 2012
Prometheus Extended Directors Cut Fan Edit 2012
Prometheus Director's Cut 2012
Prometheus Directors Cut 2012
Prometheus.(Extended.Theatrical.Version.IMAX).BluRay.1080p.2012.asdf
2001 A Space Odyssey Director's Cut (1968).mkv
2001: A Space Odyssey (Extended Directors Cut FanEdit) Bluray 1080p 1968
A Fake Movie 2035 Directors 2012.mkv
Blade Runner Director's Cut 2049.mkv
Prometheus 50th Anniversary Edition 2012.mkv
Movie 2in1 2012.mkv
Movie IMAX 2012.mkv"""
output1 = """Special.Edition.Fan Edit BRRip.x264.AAC-m2g
Despecialized mkv
Special.Edition.Remastered Bluray-1080p].mkv
Extended mkv
Extended Directors Cut Fan Edit mkv
Director's Cut mkv
Directors Cut mkv
Extended.Theatrical.Version.IMAX asdf
Director's Cut mkv
Extended Directors Cut FanEdit mkv
Directors mkv
Director's Cut mkv
50th Anniversary Edition mkv
2in1 mkv
IMAX mkv"""
inputs = input1.split("\n")
outputs = output1.split("\n")
real_o = []
for output in outputs:
real_o.append(output.split(" ")[0].replace(".", " ").strip())
count = 0
for inp in inputs:
o = real_o[count]
print "[TestCase(\"{0}\", \"{1}\")]".format(inp, o)
count += 1
+1 -1
View File
@@ -5,7 +5,7 @@ DIR=$(cd "$(dirname "$0")"; pwd)
#change these values to match your app #change these values to match your app
EXE_PATH="$DIR/Radarr.exe" EXE_PATH="$DIR/Radarr.exe"
APPNAME="Sonarr" APPNAME="Radarr"
#set up environment #set up environment
if [[ -x '/opt/local/bin/mono' ]]; then if [[ -x '/opt/local/bin/mono' ]]; then
+1 -1
View File
@@ -11,7 +11,7 @@
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>English</string> <string>English</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>Sonarr</string> <string>Radarr</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>radarr.icns</string> <string>radarr.icns</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
+38 -29
View File
@@ -23,20 +23,19 @@ if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
DAY="`date +%d`" DAY="`date +%d`"
else else
VERSION=$1 VERSION=$1
BRANCH=$2
BRANCH=${BRANCH#refs\/heads\/}
BRANCH=${BRANCH//\//-}
fi fi
outputFolder='./_output' outputFolder='./_output'
outputFolderMono='./_output_mono' outputFolderMono='./_output_mono'
outputFolderOsx='./_output_osx' outputFolderOsx='./_output_osx'
outputFolderOsxApp='./_output_osx_app' outputFolderOsxApp='./_output_osx_app'
tr -d "\r" < $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr > $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr2 tr -d "\r" < $outputFolderOsxApp/Radarr.app/Contents/MacOS/Radarr > $outputFolderOsxApp/Radarr.app/Contents/MacOS/Radarr2
rm $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr rm $outputFolderOsxApp/Radarr.app/Contents/MacOS/Radarr
chmod +x $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr2 chmod +x $outputFolderOsxApp/Radarr.app/Contents/MacOS/Radarr2
mv $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr2 $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr >& error.log mv $outputFolderOsxApp/Radarr.app/Contents/MacOS/Radarr2 $outputFolderOsxApp/Radarr.app/Contents/MacOS/Radarr >& error.log
cp -r $outputFolder/ Radarr_Windows_$VERSION
cp -r $outputFolderMono/ Radarr_Mono_$VERSION
cp -r $outputFolderOsxApp/ Radarr_OSX_$VERSION
if [ $runtime = "dotnet" ] ; then if [ $runtime = "dotnet" ] ; then
./7za.exe a Radarr_Windows_$VERSION.zip ./Radarr_Windows_$VERSION/* ./7za.exe a Radarr_Windows_$VERSION.zip ./Radarr_Windows_$VERSION/*
@@ -44,25 +43,35 @@ if [ $runtime = "dotnet" ] ; then
./7za.exe a -ttar -so Radarr_OSX_$VERSION.tar ./_output_osx/* | ./7za.exe a -si Radarr_OSX_$VERSION.tar.gz ./7za.exe a -ttar -so Radarr_OSX_$VERSION.tar ./_output_osx/* | ./7za.exe a -si Radarr_OSX_$VERSION.tar.gz
./7za.exe a -ttar -so Radarr_OSX_App_$VERSION.tar ./_output_osx_app/* | ./7za.exe a -si Radarr_OSX_App_$VERSION.tar.gz ./7za.exe a -ttar -so Radarr_OSX_App_$VERSION.tar ./_output_osx_app/* | ./7za.exe a -si Radarr_OSX_App_$VERSION.tar.gz
else else
zip -r Radarr_Windows_$VERSION.zip Radarr_Windows_$VERSION/* >& /dev/null cp -r $outputFolder/ Radarr
zip -r Radarr_Mono_$VERSION.zip Radarr_Mono_$VERSION/* >& /dev/null #TODO update for tar.gz zip -r Radarr.$BRANCH.$VERSION.windows.zip Radarr
zip -r Radarr_OSX_$VERSION_App.zip Radarr_OSX_$VERSION/* >& /dev/null rm -rf Radarr
cp -r $outputFolderMono/ Radarr
tar -zcvf Radarr.$BRANCH.$VERSION.linux.tar.gz Radarr
rm -rf Radarr
cp -r $outputFolderOsx/ Radarr
tar -zcvf Radarr.$BRANCH.$VERSION.osx.tar.gz Radarr
rm -rf Radarr
#TODO update for tar.gz
cd _output_osx_app/
zip -r ../Radarr.$BRANCH.$VERSION.osx-app.zip *
fi fi
ftp -n ftp.leonardogalli.ch << END_SCRIPT # ftp -n ftp.leonardogalli.ch << END_SCRIPT
passive # passive
quote USER $FTP_USER # quote USER $FTP_USER
quote PASS $FTP_PASS # quote PASS $FTP_PASS
mkdir builds # mkdir builds
cd builds # cd builds
mkdir $YEAR # mkdir $YEAR
cd $YEAR # cd $YEAR
mkdir $MONTH # mkdir $MONTH
cd $MONTH # cd $MONTH
mkdir $DAY # mkdir $DAY
cd $DAY # cd $DAY
binary # binary
put Radarr_Windows_$VERSION.zip # put Radarr_Windows_$VERSION.zip
put Radarr_Mono_$VERSION.zip # put Radarr_Mono_$VERSION.zip
put Radarr_OSX_$VERSION.zip # put Radarr_OSX_$VERSION.zip
quit # quit
END_SCRIPT # END_SCRIPT
@@ -70,6 +70,7 @@ namespace NzbDrone.Api.Authentication
{ {
RedirectUrl = _configFileProvider.UrlBase + "/login", RedirectUrl = _configFileProvider.UrlBase + "/login",
UserMapper = _authenticationService, UserMapper = _authenticationService,
Path = _configFileProvider.UrlBase,
CryptographyConfiguration = cryptographyConfiguration CryptographyConfiguration = cryptographyConfiguration
}); });
} }
+12 -2
View File
@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NLog;
using NzbDrone.Api.Extensions; using NzbDrone.Api.Extensions;
using NzbDrone.Api.Validation; using NzbDrone.Api.Validation;
using NzbDrone.Common; using NzbDrone.Common;
@@ -17,14 +18,17 @@ namespace NzbDrone.Api.Commands
{ {
private readonly IManageCommandQueue _commandQueueManager; private readonly IManageCommandQueue _commandQueueManager;
private readonly IServiceFactory _serviceFactory; private readonly IServiceFactory _serviceFactory;
private readonly Logger _logger;
public CommandModule(IManageCommandQueue commandQueueManager, public CommandModule(IManageCommandQueue commandQueueManager,
IBroadcastSignalRMessage signalRBroadcaster, IBroadcastSignalRMessage signalRBroadcaster,
IServiceFactory serviceFactory) IServiceFactory serviceFactory,
Logger logger)
: base(signalRBroadcaster) : base(signalRBroadcaster)
{ {
_commandQueueManager = commandQueueManager; _commandQueueManager = commandQueueManager;
_serviceFactory = serviceFactory; _serviceFactory = serviceFactory;
_logger = logger;
GetResourceById = GetCommand; GetResourceById = GetCommand;
CreateResource = StartCommand; CreateResource = StartCommand;
@@ -41,7 +45,13 @@ namespace NzbDrone.Api.Commands
private int StartCommand(CommandResource commandResource) private int StartCommand(CommandResource commandResource)
{ {
var commandType = _serviceFactory.GetImplementations(typeof(Command)) var commandType = _serviceFactory.GetImplementations(typeof(Command))
.Single(c => c.Name.Replace("Command", "").Equals(commandResource.Name, StringComparison.InvariantCultureIgnoreCase)); .SingleOrDefault(c => c.Name.Replace("Command", "").Equals(commandResource.Name, StringComparison.InvariantCultureIgnoreCase));
if (commandType == null)
{
_logger.Error("Found no matching command for {0}", commandResource.Name);
return 0;
}
dynamic command = Request.Body.FromJson(commandType); dynamic command = Request.Body.FromJson(commandType);
command.Trigger = CommandTrigger.Manual; command.Trigger = CommandTrigger.Manual;
@@ -8,9 +8,10 @@ namespace NzbDrone.Api.Config
public int MinimumAge { get; set; } public int MinimumAge { get; set; }
public int Retention { get; set; } public int Retention { get; set; }
public int RssSyncInterval { get; set; } public int RssSyncInterval { get; set; }
public int AvailabilityDelay { get; set; } public bool PreferIndexerFlags { get; set; }
public bool AllowHardcodedSubs { get; set; } public int AvailabilityDelay { get; set; }
public string WhitelistedHardcodedSubs { get; set; } public bool AllowHardcodedSubs { get; set; }
public string WhitelistedHardcodedSubs { get; set; }
} }
public static class IndexerConfigResourceMapper public static class IndexerConfigResourceMapper
@@ -22,9 +23,10 @@ namespace NzbDrone.Api.Config
MinimumAge = model.MinimumAge, MinimumAge = model.MinimumAge,
Retention = model.Retention, Retention = model.Retention,
RssSyncInterval = model.RssSyncInterval, RssSyncInterval = model.RssSyncInterval,
AvailabilityDelay = model.AvailabilityDelay, PreferIndexerFlags = model.PreferIndexerFlags,
AllowHardcodedSubs = model.AllowHardcodedSubs, AvailabilityDelay = model.AvailabilityDelay,
WhitelistedHardcodedSubs = model.WhitelistedHardcodedSubs, AllowHardcodedSubs = model.AllowHardcodedSubs,
WhitelistedHardcodedSubs = model.WhitelistedHardcodedSubs,
}; };
} }
@@ -11,6 +11,8 @@ namespace NzbDrone.Api.Config
public bool AutoDownloadPropers { get; set; } public bool AutoDownloadPropers { get; set; }
public bool CreateEmptySeriesFolders { get; set; } public bool CreateEmptySeriesFolders { get; set; }
public FileDateType FileDate { get; set; } public FileDateType FileDate { get; set; }
public bool AutoRenameFolders { get; set; }
public bool PathsDefaultStatic { get; set; }
public bool SetPermissionsLinux { get; set; } public bool SetPermissionsLinux { get; set; }
public string FileChmod { get; set; } public string FileChmod { get; set; }
@@ -35,6 +37,8 @@ namespace NzbDrone.Api.Config
AutoDownloadPropers = model.AutoDownloadPropers, AutoDownloadPropers = model.AutoDownloadPropers,
CreateEmptySeriesFolders = model.CreateEmptySeriesFolders, CreateEmptySeriesFolders = model.CreateEmptySeriesFolders,
FileDate = model.FileDate, FileDate = model.FileDate,
AutoRenameFolders = model.AutoRenameFolders,
PathsDefaultStatic = model.PathsDefaultStatic,
SetPermissionsLinux = model.SetPermissionsLinux, SetPermissionsLinux = model.SetPermissionsLinux,
FileChmod = model.FileChmod, FileChmod = model.FileChmod,
+2 -1
View File
@@ -46,6 +46,7 @@ namespace NzbDrone.Api.Indexers
public bool DownloadAllowed { get; set; } public bool DownloadAllowed { get; set; }
public int ReleaseWeight { get; set; } public int ReleaseWeight { get; set; }
public IEnumerable<string> IndexerFlags { get; set; }
public string MagnetUrl { get; set; } public string MagnetUrl { get; set; }
public string InfoHash { get; set; } public string InfoHash { get; set; }
@@ -132,7 +133,7 @@ namespace NzbDrone.Api.Indexers
Seeders = torrentInfo.Seeders, Seeders = torrentInfo.Seeders,
Leechers = (torrentInfo.Peers.HasValue && torrentInfo.Seeders.HasValue) ? (torrentInfo.Peers.Value - torrentInfo.Seeders.Value) : (int?)null, Leechers = (torrentInfo.Peers.HasValue && torrentInfo.Seeders.HasValue) ? (torrentInfo.Peers.Value - torrentInfo.Seeders.Value) : (int?)null,
Protocol = releaseInfo.DownloadProtocol, Protocol = releaseInfo.DownloadProtocol,
IndexerFlags = torrentInfo.IndexerFlags.ToString().Split(new string[] { ", " }, StringSplitOptions.None),
Edition = parsedMovieInfo.Edition, Edition = parsedMovieInfo.Edition,
IsDaily = false, IsDaily = false,
+4 -1
View File
@@ -46,11 +46,14 @@ namespace NzbDrone.Api.EpisodeFiles
return movie.ToResource(); return movie.ToResource();
} }
private void SetQuality(MovieFileResource movieFileResource) private void SetQuality(MovieFileResource movieFileResource)
{ {
var movieFile = _mediaFileService.GetMovie(movieFileResource.Id); var movieFile = _mediaFileService.GetMovie(movieFileResource.Id);
movieFile.Quality = movieFileResource.Quality; movieFile.Quality = movieFileResource.Quality;
_mediaFileService.Update(movieFile); _mediaFileService.Update(movieFile);
BroadcastResourceChange(ModelAction.Updated, movieFile.Id);
} }
private void DeleteMovieFile(int id) private void DeleteMovieFile(int id)
@@ -0,0 +1,45 @@
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Api.ClientSchema;
using NzbDrone.Core.NetImport;
using NzbDrone.Core.NetImport.ImportExclusions;
using NzbDrone.Core.Validation.Paths;
namespace NzbDrone.Api.NetImport
{
public class ImportExclusionsModule : NzbDroneRestModule<ImportExclusionsResource>
{
private readonly IImportExclusionsService _exclusionService;
public ImportExclusionsModule(NetImportFactory netImportFactory, IImportExclusionsService exclusionService) : base("exclusions")
{
_exclusionService = exclusionService;
GetResourceAll = GetAll;
CreateResource = AddExclusion;
DeleteResource = RemoveExclusion;
GetResourceById = GetById;
}
public List<ImportExclusionsResource> GetAll()
{
return _exclusionService.GetAllExclusions().ToResource();
}
public ImportExclusionsResource GetById(int id)
{
return _exclusionService.GetById(id).ToResource();
}
public int AddExclusion(ImportExclusionsResource exclusionResource)
{
var model = exclusionResource.ToModel();
return _exclusionService.AddExclusion(model).Id;
}
public void RemoveExclusion (int id)
{
_exclusionService.RemoveExclusion(new ImportExclusion { Id = id });
}
}
}
@@ -0,0 +1,46 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.NetImport;
using NzbDrone.Core.Tv;
namespace NzbDrone.Api.NetImport
{
public class ImportExclusionsResource : ProviderResource
{
//public int Id { get; set; }
public int TmdbId { get; set; }
public string MovieTitle { get; set; }
public int MovieYear { get; set; }
}
public static class ImportExclusionsResourceMapper
{
public static ImportExclusionsResource ToResource(this Core.NetImport.ImportExclusions.ImportExclusion model)
{
if (model == null) return null;
return new ImportExclusionsResource
{
Id = model.Id,
TmdbId = model.TmdbId,
MovieTitle = model.MovieTitle,
MovieYear = model.MovieYear
};
}
public static List<ImportExclusionsResource> ToResource(this IEnumerable<Core.NetImport.ImportExclusions.ImportExclusion> exclusions)
{
return exclusions.Select(ToResource).ToList();
}
public static Core.NetImport.ImportExclusions.ImportExclusion ToModel(this ImportExclusionsResource resource)
{
return new Core.NetImport.ImportExclusions.ImportExclusion
{
TmdbId = resource.TmdbId,
MovieTitle = resource.MovieTitle,
MovieYear = resource.MovieYear
};
}
}
}
+3
View File
@@ -270,6 +270,9 @@
<Compile Include="Wanted\MissingModule.cs" /> <Compile Include="Wanted\MissingModule.cs" />
<Compile Include="Wanted\MovieCutoffModule.cs" /> <Compile Include="Wanted\MovieCutoffModule.cs" />
<Compile Include="Wanted\MovieMissingModule.cs" /> <Compile Include="Wanted\MovieMissingModule.cs" />
<Compile Include="Series\MovieDiscoverModule.cs" />
<Compile Include="NetImport\ImportExclusionsModule.cs" />
<Compile Include="NetImport\ImportExclusionsResource.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="app.config" /> <None Include="app.config" />
@@ -0,0 +1,44 @@
using System.Collections.Generic;
using Nancy;
using NzbDrone.Api.Extensions;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MetadataSource;
using System.Linq;
using System;
using NzbDrone.Api.REST;
namespace NzbDrone.Api.Movie
{
public class MovieDiscoverModule : NzbDroneRestModule<MovieResource>
{
private readonly IDiscoverNewMovies _searchProxy;
public MovieDiscoverModule(IDiscoverNewMovies searchProxy)
: base("/movies/discover")
{
_searchProxy = searchProxy;
Get["/{action?recommendations}"] = x => Search(x.action);
}
private Response Search(string action)
{
var imdbResults = _searchProxy.DiscoverNewMovies(action);
return MapToResource(imdbResults).AsResponse();
}
private static IEnumerable<MovieResource> MapToResource(IEnumerable<Core.Tv.Movie> movies)
{
foreach (var currentSeries in movies)
{
var resource = currentSeries.ToResource();
var poster = currentSeries.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
if (poster != null)
{
resource.RemotePoster = poster.Url;
}
yield return resource;
}
}
}
}
+29 -6
View File
@@ -17,6 +17,8 @@ using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Validation; using NzbDrone.Core.Validation;
using NzbDrone.SignalR; using NzbDrone.SignalR;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using Microsoft.CSharp.RuntimeBinder;
using Nancy;
namespace NzbDrone.Api.Movie namespace NzbDrone.Api.Movie
{ {
@@ -58,9 +60,12 @@ namespace NzbDrone.Api.Movie
GetResourceAll = AllMovie; GetResourceAll = AllMovie;
GetResourcePaged = GetMoviePaged; GetResourcePaged = GetMoviePaged;
GetResourceById = GetMovie; GetResourceById = GetMovie;
Get[TITLE_SLUG_ROUTE] = (options) => { Get[TITLE_SLUG_ROUTE] = GetByTitleSlug; /*(options) => {
return ReqResExtensions.AsResponse(GetByTitleSlug(options.slug)); return ReqResExtensions.AsResponse(GetByTitleSlug(options.slug), Nancy.HttpStatusCode.OK);
}; };*/
CreateResource = AddMovie; CreateResource = AddMovie;
UpdateResource = UpdateMovie; UpdateResource = UpdateMovie;
DeleteResource = DeleteMovie; DeleteResource = DeleteMovie;
@@ -145,9 +150,27 @@ namespace NzbDrone.Api.Movie
return moviesResources; return moviesResources;
} }
private MovieResource GetByTitleSlug(string slug) private Response GetByTitleSlug(dynamic options)
{ {
return MapToResource(_moviesService.FindByTitleSlug(slug)); var slug = "";
try
{
slug = options.slug;
// do stuff with x
}
catch (RuntimeBinderException)
{
return new NotFoundResponse();
}
try
{
return MapToResource(_moviesService.FindByTitleSlug(slug)).AsResponse(Nancy.HttpStatusCode.OK);
}
catch (ModelNotFoundException)
{
return new NotFoundResponse();
}
} }
private int AddMovie(MovieResource moviesResource) private int AddMovie(MovieResource moviesResource)
@@ -213,7 +236,7 @@ namespace NzbDrone.Api.Movie
private void LinkMovieStatistics(MovieResource resource, MovieStatistics moviesStatistics) private void LinkMovieStatistics(MovieResource resource, MovieStatistics moviesStatistics)
{ {
resource.SizeOnDisk = moviesStatistics.SizeOnDisk; //resource.SizeOnDisk = 0;//TODO: incorporate movie statistics moviesStatistics.SizeOnDisk;
} }
private void PopulateAlternateTitles(List<MovieResource> resources) private void PopulateAlternateTitles(List<MovieResource> resources)
+6 -2
View File
@@ -40,6 +40,7 @@ namespace NzbDrone.Api.Movie
//View & Edit //View & Edit
public string Path { get; set; } public string Path { get; set; }
public int ProfileId { get; set; } public int ProfileId { get; set; }
public MoviePathState PathState { get; set; }
//Editing Only //Editing Only
public bool Monitored { get; set; } public bool Monitored { get; set; }
@@ -120,7 +121,7 @@ namespace NzbDrone.Api.Movie
//TotalEpisodeCount //TotalEpisodeCount
//EpisodeCount //EpisodeCount
//EpisodeFileCount //EpisodeFileCount
//SizeOnDisk SizeOnDisk = size,
Status = model.Status, Status = model.Status,
Overview = model.Overview, Overview = model.Overview,
//NextAiring //NextAiring
@@ -131,6 +132,7 @@ namespace NzbDrone.Api.Movie
Path = model.Path, Path = model.Path,
ProfileId = model.ProfileId, ProfileId = model.ProfileId,
PathState = model.PathState,
Monitored = model.Monitored, Monitored = model.Monitored,
MinimumAvailability = model.MinimumAvailability, MinimumAvailability = model.MinimumAvailability,
@@ -138,7 +140,7 @@ namespace NzbDrone.Api.Movie
IsAvailable = model.IsAvailable(), IsAvailable = model.IsAvailable(),
FolderName = model.FolderName(), FolderName = model.FolderName(),
SizeOnDisk = size, //SizeOnDisk = size,
Runtime = model.Runtime, Runtime = model.Runtime,
LastInfoSync = model.LastInfoSync, LastInfoSync = model.LastInfoSync,
@@ -187,6 +189,7 @@ namespace NzbDrone.Api.Movie
Path = resource.Path, Path = resource.Path,
ProfileId = resource.ProfileId, ProfileId = resource.ProfileId,
PathState = resource.PathState,
Monitored = resource.Monitored, Monitored = resource.Monitored,
MinimumAvailability = resource.MinimumAvailability, MinimumAvailability = resource.MinimumAvailability,
@@ -217,6 +220,7 @@ namespace NzbDrone.Api.Movie
movie.Path = resource.Path; movie.Path = resource.Path;
movie.ProfileId = resource.ProfileId; movie.ProfileId = resource.ProfileId;
movie.PathState = resource.PathState;
movie.Monitored = resource.Monitored; movie.Monitored = resource.Monitored;
movie.MinimumAvailability = resource.MinimumAvailability; movie.MinimumAvailability = resource.MinimumAvailability;
@@ -29,7 +29,7 @@ namespace NzbDrone.Common.Test.DiskTests
public void should_be_able_to_check_space_on_ramdrive() public void should_be_able_to_check_space_on_ramdrive()
{ {
MonoOnly(); MonoOnly();
Subject.GetAvailableSpace("/run/").Should().NotBe(0); Subject.GetAvailableSpace("/").Should().NotBe(0);
} }
[Test] [Test]
@@ -20,7 +20,6 @@ namespace NzbDrone.Common.Test
} }
[TestCase("")] [TestCase("")]
[TestCase("http://")]
public void DownloadString_should_throw_on_error(string url) public void DownloadString_should_throw_on_error(string url)
{ {
Assert.Throws<ArgumentException>(() => Subject.DownloadString(url)); Assert.Throws<ArgumentException>(() => Subject.DownloadString(url));
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -80,5 +80,30 @@ namespace NzbDrone.Common.Extensions
{ {
return source.Select(predicate).ToList(); return source.Select(predicate).ToList();
} }
public static IEnumerable<T> DropLast<T>(this IEnumerable<T> source, int n)
{
if (source == null)
throw new ArgumentNullException("source");
if (n < 0)
throw new ArgumentOutOfRangeException("n",
"Argument n should be non-negative.");
return InternalDropLast(source, n);
}
private static IEnumerable<T> InternalDropLast<T>(IEnumerable<T> source, int n)
{
Queue<T> buffer = new Queue<T>(n + 1);
foreach (T x in source)
{
buffer.Enqueue(x);
if (buffer.Count == n + 1)
yield return buffer.Dequeue();
}
}
} }
} }
@@ -20,6 +20,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private RemoteEpisode parseResultMulti; private RemoteEpisode parseResultMulti;
private RemoteEpisode parseResultSingle; private RemoteEpisode parseResultSingle;
private Series series; private Series series;
private Movie movie;
private RemoteMovie remoteMovie;
private QualityDefinition qualityType; private QualityDefinition qualityType;
[SetUp] [SetUp]
@@ -28,6 +30,16 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
series = Builder<Series>.CreateNew() series = Builder<Series>.CreateNew()
.Build(); .Build();
movie = Builder<Movie>.CreateNew().Build();
remoteMovie = new RemoteMovie
{
Movie = movie,
Release = new ReleaseInfo(),
ParsedMovieInfo = new ParsedMovieInfo { Quality = new QualityModel(Quality.SDTV, new Revision(version: 2)) },
};
parseResultMultiSet = new RemoteEpisode parseResultMultiSet = new RemoteEpisode
{ {
Series = series, Series = series,
@@ -216,5 +228,17 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.IsSatisfiedBy(parseResultSingle, null).Accepted.Should().BeTrue(); Subject.IsSatisfiedBy(parseResultSingle, null).Accepted.Should().BeTrue();
} }
[Test]
public void should_use_110_minutes_if_runtime_is_0()
{
movie.Runtime = 0;
remoteMovie.Movie = movie;
remoteMovie.Release.Size = 1095.Megabytes();
Subject.IsSatisfiedBy(remoteMovie, null).Accepted.Should().Be(true);
remoteMovie.Release.Size = 1105.Megabytes();
Subject.IsSatisfiedBy(remoteMovie, null).Accepted.Should().Be(false);
}
} }
} }
@@ -145,8 +145,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
[TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading)] [TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading)]
[TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued)] [TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued)]
[TestCase(TransmissionTorrentStatus.Downloading, DownloadItemStatus.Downloading)] [TestCase(TransmissionTorrentStatus.Downloading, DownloadItemStatus.Downloading)]
[TestCase(TransmissionTorrentStatus.SeedingWait, DownloadItemStatus.Completed)] [TestCase(TransmissionTorrentStatus.SeedingWait, DownloadItemStatus.Downloading)]
[TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed)] [TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Downloading)]
public void GetItems_should_return_queued_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus) public void GetItems_should_return_queued_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus)
{ {
_queued.Status = apiStatus; _queued.Status = apiStatus;
@@ -160,7 +160,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
[TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued)] [TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued)]
[TestCase(TransmissionTorrentStatus.Downloading, DownloadItemStatus.Downloading)] [TestCase(TransmissionTorrentStatus.Downloading, DownloadItemStatus.Downloading)]
[TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed)] [TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Downloading)]
public void GetItems_should_return_downloading_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus) public void GetItems_should_return_downloading_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus)
{ {
_downloading.Status = apiStatus; _downloading.Status = apiStatus;
@@ -13,6 +13,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
[TestFixture] [TestFixture]
public class VuzeFixture : TransmissionFixtureBase<Vuze> public class VuzeFixture : TransmissionFixtureBase<Vuze>
{ {
[SetUp]
public void Setup_Vuze()
{
// Vuze never sets isFinished.
_completed.IsFinished = false;
}
[Test] [Test]
public void queued_item_should_have_required_properties() public void queued_item_should_have_required_properties()
{ {
@@ -147,8 +154,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
[TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading)] [TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading)]
[TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued)] [TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued)]
[TestCase(TransmissionTorrentStatus.Downloading, DownloadItemStatus.Downloading)] [TestCase(TransmissionTorrentStatus.Downloading, DownloadItemStatus.Downloading)]
[TestCase(TransmissionTorrentStatus.SeedingWait, DownloadItemStatus.Completed)] [TestCase(TransmissionTorrentStatus.SeedingWait, DownloadItemStatus.Downloading)]
[TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed)] [TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Downloading)]
public void GetItems_should_return_queued_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus) public void GetItems_should_return_queued_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus)
{ {
_queued.Status = apiStatus; _queued.Status = apiStatus;
@@ -162,7 +169,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
[TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued)] [TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued)]
[TestCase(TransmissionTorrentStatus.Downloading, DownloadItemStatus.Downloading)] [TestCase(TransmissionTorrentStatus.Downloading, DownloadItemStatus.Downloading)]
[TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed)] [TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Downloading)]
public void GetItems_should_return_downloading_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus) public void GetItems_should_return_downloading_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus)
{ {
_downloading.Status = apiStatus; _downloading.Status = apiStatus;
@@ -177,7 +184,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
[TestCase(TransmissionTorrentStatus.Stopped, DownloadItemStatus.Completed, false)] [TestCase(TransmissionTorrentStatus.Stopped, DownloadItemStatus.Completed, false)]
[TestCase(TransmissionTorrentStatus.CheckWait, DownloadItemStatus.Downloading, true)] [TestCase(TransmissionTorrentStatus.CheckWait, DownloadItemStatus.Downloading, true)]
[TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading, true)] [TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading, true)]
[TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Completed, true)] [TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued, true)]
[TestCase(TransmissionTorrentStatus.SeedingWait, DownloadItemStatus.Completed, true)] [TestCase(TransmissionTorrentStatus.SeedingWait, DownloadItemStatus.Completed, true)]
[TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed, true)] [TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed, true)]
public void GetItems_should_return_completed_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus, bool expectedReadOnly) public void GetItems_should_return_completed_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus, bool expectedReadOnly)
@@ -294,7 +301,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
} }
[Test] [Test]
public void should_have_correct_output_directory() public void should_have_correct_output_directory_for_multifile_torrents()
{ {
WindowsOnly(); WindowsOnly();
@@ -311,5 +318,25 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
items.First().OutputPath.Should().Be(@"C:\Downloads\" + _title); items.First().OutputPath.Should().Be(@"C:\Downloads\" + _title);
} }
[Test]
public void should_have_correct_output_directory_for_singlefile_torrents()
{
WindowsOnly();
var fileName = _title + ".mkv";
_downloading.Name = fileName;
_downloading.DownloadDir = @"C:/Downloads";
GivenTorrents(new List<TransmissionTorrent>
{
_downloading
});
var items = Subject.GetItems().ToList();
items.Should().HaveCount(1);
items.First().OutputPath.Should().Be(@"C:\Downloads\" + fileName);
}
} }
} }
File diff suppressed because one or more lines are too long
@@ -0,0 +1,69 @@
using System;
using System.Linq;
using System.Text;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.PassThePopcorn;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.IndexerTests.PTPTests
{
[TestFixture]
public class PTPFixture : CoreTest<PassThePopcorn>
{
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
{
Name = "PTP",
Settings = new PassThePopcornSettings() { Passkey = "fakekey", Username = "asdf", Password = "sad" }
};
}
[TestCase("Files/Indexers/PTP/imdbsearch.json")]
public void should_parse_feed_from_PTP(string fileName)
{
var authResponse = new PassThePopcornAuthResponse { Result = "Ok" };
System.IO.StringWriter authStream = new System.IO.StringWriter();
Json.Serialize(authResponse, authStream);
var responseJson = ReadAllText(fileName);
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.POST)))
.Returns<HttpRequest>(r => new HttpResponse(r,new HttpHeader(), authStream.ToString()));
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader {ContentType = HttpAccept.Json.Value}, responseJson));
var torrents = Subject.FetchRecent();
torrents.Should().HaveCount(293);
torrents.First().Should().BeOfType<PassThePopcornInfo>();
var first = torrents.First() as TorrentInfo;
first.Guid.Should().Be("PassThePopcorn-483521");
first.Title.Should().Be("The.Night.Of.S01.720p.HDTV.x264-BTN");
first.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
first.DownloadUrl.Should().Be("https://passthepopcorn.me/torrents.php?action=download&id=483521&authkey=00000000000000000000000000000000&torrent_pass=00000000000000000000000000000000");
first.InfoUrl.Should().Be("https://passthepopcorn.me/torrents.php?id=148131&torrentid=483521");
//first.PublishDate.Should().Be(DateTime.Parse("2017-04-17T12:13:42+0000").ToUniversalTime()); stupid timezones
first.Size.Should().Be(9370933376);
first.InfoHash.Should().BeNullOrEmpty();
first.MagnetUrl.Should().BeNullOrEmpty();
first.Peers.Should().Be(3);
first.Seeders.Should().Be(1);
torrents.Any(t => t.IndexerFlags.HasFlag(IndexerFlags.G_Freeleech)).Should().Be(true);
}
}
}
@@ -296,6 +296,7 @@
<Compile Include="OrganizerTests\FileNameBuilderTests\CleanTitleFixture.cs" /> <Compile Include="OrganizerTests\FileNameBuilderTests\CleanTitleFixture.cs" />
<Compile Include="OrganizerTests\FileNameBuilderTests\EpisodeTitleCollapseFixture.cs" /> <Compile Include="OrganizerTests\FileNameBuilderTests\EpisodeTitleCollapseFixture.cs" />
<Compile Include="OrganizerTests\FileNameBuilderTests\MultiEpisodeFixture.cs" /> <Compile Include="OrganizerTests\FileNameBuilderTests\MultiEpisodeFixture.cs" />
<Compile Include="OrganizerTests\GetMovieFolderFixture.cs" />
<Compile Include="ParserTests\MiniSeriesEpisodeParserFixture.cs" /> <Compile Include="ParserTests\MiniSeriesEpisodeParserFixture.cs" />
<Compile Include="ParserTests\RomanNumeralTests\RomanNumeralConversionFixture.cs" /> <Compile Include="ParserTests\RomanNumeralTests\RomanNumeralConversionFixture.cs" />
<Compile Include="Qualities\RevisionComparableFixture.cs" /> <Compile Include="Qualities\RevisionComparableFixture.cs" />
@@ -389,6 +390,10 @@
<Content Include="Files\ArabicRomanNumeralDictionary.JSON"> <Content Include="Files\ArabicRomanNumeralDictionary.JSON">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Compile Include="IndexerTests\PTPTests\PTPFixture.cs" />
<None Include="Files\Indexers\PTP\imdbsearch.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Marr.Data\Marr.Data.csproj"> <ProjectReference Include="..\Marr.Data\Marr.Data.csproj">
@@ -574,6 +579,8 @@
<Folder Include="DataAugmentation\SceneNumbering\" /> <Folder Include="DataAugmentation\SceneNumbering\" />
<Folder Include="Providers\" /> <Folder Include="Providers\" />
<Folder Include="ProviderTests\UpdateProviderTests\" /> <Folder Include="ProviderTests\UpdateProviderTests\" />
<Folder Include="IndexerTests\PTPTests\" />
<Folder Include="Files\Indexers\PTP\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" /> <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
@@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using FizzWare.NBuilder; using FizzWare.NBuilder;
@@ -734,5 +734,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile) Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be(releaseGroup); .Should().Be(releaseGroup);
} }
} }
} }
@@ -0,0 +1,40 @@
using NUnit.Framework;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Test.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NzbDrone.Core.Tv;
using FluentAssertions;
namespace NzbDrone.Core.Test.OrganizerTests
{
[TestFixture]
public class GetMovieFolderFixture : CoreTest<FileNameBuilder>
{
private NamingConfig namingConfig;
[SetUp]
public void Setup()
{
namingConfig = NamingConfig.Default;
Mocker.GetMock<INamingConfigService>()
.Setup(c => c.GetConfig()).Returns(namingConfig);
}
[TestCase("Arrival", 2016, "{Movie Title} ({Release Year})", "Arrival (2016)")]
[TestCase("The Big Short", 2015, "{Movie TitleThe} ({Release Year})", "Big Short, The (2015)")]
[TestCase("The Big Short", 2015, "{Movie Title} ({Release Year})", "The Big Short (2015)")]
public void should_use_movieFolderFormat_to_build_folder_name(string movieTitle, int year, string format, string expected)
{
namingConfig.MovieFolderFormat = format;
var movie = new Movie { Title = movieTitle, Year = year };
Subject.GetMovieFolder(movie).Should().Be(expected);
}
}
}
@@ -1,4 +1,4 @@
using FluentAssertions; using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
@@ -74,6 +74,8 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("To.Live.and.Die.in.L.A.1985.1080p.BluRay", "To Live and Die in L.A.")] [TestCase("To.Live.and.Die.in.L.A.1985.1080p.BluRay", "To Live and Die in L.A.")]
[TestCase("A.I.Artificial.Intelligence.(2001)", "A.I. Artificial Intelligence")] [TestCase("A.I.Artificial.Intelligence.(2001)", "A.I. Artificial Intelligence")]
[TestCase("A.Movie.Name.(1998)", "A Movie Name")] [TestCase("A.Movie.Name.(1998)", "A Movie Name")]
[TestCase("Thor: The Dark World 2013", "Thor The Dark World")]
[TestCase("Resident.Evil.The.Final.Chapter.2016", "Resident Evil The Final Chapter")]
public void should_parse_movie_title(string postTitle, string title) public void should_parse_movie_title(string postTitle, string title)
{ {
Parser.Parser.ParseMovieTitle(postTitle).MovieTitle.Should().Be(title); Parser.Parser.ParseMovieTitle(postTitle).MovieTitle.Should().Be(title);
@@ -86,9 +88,48 @@ namespace NzbDrone.Core.Test.ParserTests
} }
[TestCase("The Danish Girl 2015")] [TestCase("The Danish Girl 2015")]
[TestCase("The.Danish.Girl.2015.1080p.BluRay.x264.DTS-HD.MA.5.1-RARBG")]
public void should_not_parse_language_in_movie_title(string postTitle) public void should_not_parse_language_in_movie_title(string postTitle)
{ {
Parser.Parser.ParseMovieTitle(postTitle).Language.Should().Be(Language.English); Parser.Parser.ParseMovieTitle(postTitle).Language.Should().Be(Language.English);
} }
[TestCase("Prometheus 2012 Directors Cut", "Directors Cut")]
[TestCase("Star Wars Episode IV - A New Hope 1999 (Despecialized).mkv", "Despecialized")]
[TestCase("Prometheus.2012.(Special.Edition.Remastered).[Bluray-1080p].mkv", "Special Edition Remastered")]
[TestCase("Prometheus 2012 Extended", "Extended")]
[TestCase("Prometheus 2012 Extended Directors Cut Fan Edit", "Extended Directors Cut Fan Edit")]
[TestCase("Prometheus 2012 Director's Cut", "Director's Cut")]
[TestCase("Prometheus 2012 Directors Cut", "Directors Cut")]
[TestCase("Prometheus.2012.(Extended.Theatrical.Version.IMAX).BluRay.1080p.2012.asdf", "Extended Theatrical Version IMAX")]
[TestCase("2001 A Space Odyssey (1968) Director's Cut .mkv", "Director's Cut")]
[TestCase("2001: A Space Odyssey 1968 (Extended Directors Cut FanEdit)", "Extended Directors Cut FanEdit")]
[TestCase("A Fake Movie 2035 2012 Directors.mkv", "Directors")]
[TestCase("Blade Runner 2049 Director's Cut.mkv", "Director's Cut")]
[TestCase("Prometheus 2012 50th Anniversary Edition.mkv", "50th Anniversary Edition")]
[TestCase("Movie 2012 2in1.mkv", "2in1")]
[TestCase("Movie 2012 IMAX.mkv", "IMAX")]
[TestCase("Movie 2012 Restored.mkv", "Restored")]
[TestCase("Prometheus.Special.Edition.Fan Edit.2012..BRRip.x264.AAC-m2g", "Special Edition Fan Edit")]
[TestCase("Star Wars Episode IV - A New Hope (Despecialized) 1999.mkv", "Despecialized")]
[TestCase("Prometheus.(Special.Edition.Remastered).2012.[Bluray-1080p].mkv", "Special Edition Remastered")]
[TestCase("Prometheus Extended 2012", "Extended")]
[TestCase("Prometheus Extended Directors Cut Fan Edit 2012", "Extended Directors Cut Fan Edit")]
[TestCase("Prometheus Director's Cut 2012", "Director's Cut")]
[TestCase("Prometheus Directors Cut 2012", "Directors Cut")]
[TestCase("Prometheus.(Extended.Theatrical.Version.IMAX).BluRay.1080p.2012.asdf", "Extended Theatrical Version IMAX")]
[TestCase("2001 A Space Odyssey Director's Cut (1968).mkv", "Director's Cut")]
[TestCase("2001: A Space Odyssey (Extended Directors Cut FanEdit) Bluray 1080p 1968", "Extended Directors Cut FanEdit")]
[TestCase("A Fake Movie 2035 Directors 2012.mkv", "Directors")]
[TestCase("Blade Runner Director's Cut 2049.mkv", "Director's Cut")]
[TestCase("Prometheus 50th Anniversary Edition 2012.mkv", "50th Anniversary Edition")]
[TestCase("Movie 2in1 2012.mkv", "2in1")]
[TestCase("Movie IMAX 2012.mkv", "IMAX")]
[TestCase("Fake Movie Final Cut 2016", "Final Cut")]
[TestCase("Fake Movie 2016 Final Cut ", "Final Cut")]
public void should_parse_edition(string postTitle, string edition)
{
Parser.Parser.ParseMovieTitle(postTitle).Edition.Should().Be(edition);
}
} }
} }
@@ -23,6 +23,8 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
private ParsedMovieInfo _wrongYearInfo; private ParsedMovieInfo _wrongYearInfo;
private ParsedMovieInfo _romanTitleInfo; private ParsedMovieInfo _romanTitleInfo;
private ParsedMovieInfo _alternativeTitleInfo; private ParsedMovieInfo _alternativeTitleInfo;
private ParsedMovieInfo _umlautInfo;
private ParsedMovieInfo _umlautAltInfo;
private MovieSearchCriteria _movieSearchCriteria; private MovieSearchCriteria _movieSearchCriteria;
private List<Episode> _episodes; private List<Episode> _episodes;
private ParsedEpisodeInfo _parsedEpisodeInfo; private ParsedEpisodeInfo _parsedEpisodeInfo;
@@ -37,10 +39,10 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
.Build(); .Build();
_movie = Builder<Movie>.CreateNew() _movie = Builder<Movie>.CreateNew()
.With(m => m.Title = "Mission Impossible 3") .With(m => m.Title = "Fack Ju Göthe 2")
.With(m => m.CleanTitle = "missionimpossible3") .With(m => m.CleanTitle = "fackjugoethe2")
.With(m => m.Year = 2006) .With(m => m.Year = 2015)
.With(m => m.AlternativeTitles = new List<string> { "Mission Impossible 3: Same same" }) .With(m => m.AlternativeTitles = new List<string> { "Fack Ju Göthe 2: Same same" })
.Build(); .Build();
_episodes = Builder<Episode>.CreateListOfSize(1) _episodes = Builder<Episode>.CreateListOfSize(1)
@@ -77,10 +79,22 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
_romanTitleInfo = new ParsedMovieInfo _romanTitleInfo = new ParsedMovieInfo
{ {
MovieTitle = "Mission Impossible III", MovieTitle = "Fack Ju Göthe II",
Year = _movie.Year, Year = _movie.Year,
}; };
_umlautInfo = new ParsedMovieInfo
{
MovieTitle = "Fack Ju Goethe 2",
Year = _movie.Year
};
_umlautAltInfo = new ParsedMovieInfo
{
MovieTitle = "Fack Ju Goethe 2: Same same",
Year = _movie.Year
};
_singleEpisodeSearchCriteria = new SingleEpisodeSearchCriteria _singleEpisodeSearchCriteria = new SingleEpisodeSearchCriteria
{ {
Series = _series, Series = _series,
@@ -115,7 +129,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
Subject.Map(_parsedMovieInfo, "", null); Subject.Map(_parsedMovieInfo, "", null);
Mocker.GetMock<IMovieService>() Mocker.GetMock<IMovieService>()
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Once()); .Verify(v => v.FindByTitle(It.IsAny<string>(), It.IsAny<int>()), Times.Once());
} }
[Test] [Test]
@@ -148,5 +162,12 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
Subject.Map(_romanTitleInfo, "", _movieSearchCriteria).Movie.Should().Be(_movieSearchCriteria.Movie); Subject.Map(_romanTitleInfo, "", _movieSearchCriteria).Movie.Should().Be(_movieSearchCriteria.Movie);
} }
[Test]
public void should_match_umlauts()
{
Subject.Map(_umlautInfo, "", _movieSearchCriteria).Movie.Should().Be(_movieSearchCriteria.Movie);
Subject.Map(_umlautAltInfo, "", _movieSearchCriteria).Movie.Should().Be(_movieSearchCriteria.Movie);
}
} }
} }
@@ -105,11 +105,11 @@ namespace NzbDrone.Core.Configuration
set { SetValue("RssSyncInterval", value); } set { SetValue("RssSyncInterval", value); }
} }
public int AvailabilityDelay public int AvailabilityDelay
{ {
get { return GetValueInt("AvailabilityDelay",0); } get { return GetValueInt("AvailabilityDelay",0); }
set { SetValue("AvailabilityDelay", value); } set { SetValue("AvailabilityDelay", value); }
} }
public int NetImportSyncInterval public int NetImportSyncInterval
{ {
@@ -190,19 +190,26 @@ namespace NzbDrone.Core.Configuration
set { SetValue("EnableCompletedDownloadHandling", value); } set { SetValue("EnableCompletedDownloadHandling", value); }
} }
public bool AllowHardcodedSubs public bool PreferIndexerFlags
{ {
get { return GetValueBoolean("AllowHardcodedSubs", false); } get { return GetValueBoolean("PreferIndexerFlags", false); }
set { SetValue("AllowHardcodedSubs", value); } set {SetValue("PreferIndexerFlags", value);}
} }
public string WhitelistedHardcodedSubs public bool AllowHardcodedSubs
{ {
get { return GetValue("WhitelistedHardcodedSubs", ""); } get { return GetValueBoolean("AllowHardcodedSubs", false); }
set { SetValue("WhitelistedHardcodedSubs", value); } set { SetValue("AllowHardcodedSubs", value); }
} }
public string WhitelistedHardcodedSubs
{
get { return GetValue("WhitelistedHardcodedSubs", ""); }
set { SetValue("WhitelistedHardcodedSubs", value); }
}
public bool RemoveCompletedDownloads public bool RemoveCompletedDownloads
{ {
@@ -287,6 +294,20 @@ namespace NzbDrone.Core.Configuration
set { SetValue("ExtraFileExtensions", value); } set { SetValue("ExtraFileExtensions", value); }
} }
public bool AutoRenameFolders
{
get { return GetValueBoolean("AutoRenameFolders", false); }
set { SetValue("AutoRenameFolders", value); }
}
public bool PathsDefaultStatic
{
get { return GetValueBoolean("PathsDefaultStatic", true); }
set { SetValue("PathsDefaultStatic", value); }
}
public bool SetPermissionsLinux public bool SetPermissionsLinux
{ {
get { return GetValueBoolean("SetPermissionsLinux", false); } get { return GetValueBoolean("SetPermissionsLinux", false); }
@@ -33,6 +33,8 @@ namespace NzbDrone.Core.Configuration
bool CopyUsingHardlinks { get; set; } bool CopyUsingHardlinks { get; set; }
bool EnableMediaInfo { get; set; } bool EnableMediaInfo { get; set; }
string ExtraFileExtensions { get; set; } string ExtraFileExtensions { get; set; }
bool AutoRenameFolders { get; set; }
bool PathsDefaultStatic { get; set; }
//Permissions (Media Management) //Permissions (Media Management)
bool SetPermissionsLinux { get; set; } bool SetPermissionsLinux { get; set; }
@@ -46,20 +48,22 @@ namespace NzbDrone.Core.Configuration
int RssSyncInterval { get; set; } int RssSyncInterval { get; set; }
int MinimumAge { get; set; } int MinimumAge { get; set; }
int AvailabilityDelay { get; set; } bool PreferIndexerFlags { get; set; }
bool AllowHardcodedSubs { get; set; } int AvailabilityDelay { get; set; }
string WhitelistedHardcodedSubs { get; set; }
bool AllowHardcodedSubs { get; set; }
string WhitelistedHardcodedSubs { get; set; }
int NetImportSyncInterval { get; set; } int NetImportSyncInterval { get; set; }
string ListSyncLevel { get; set; } string ListSyncLevel { get; set; }
string ImportExclusions { get; set; } string ImportExclusions { get; set; }
string TraktAuthToken { get; set; } string TraktAuthToken { get; set; }
string TraktRefreshToken { get; set; } string TraktRefreshToken { get; set; }
int TraktTokenExpiry { get; set; } int TraktTokenExpiry { get; set; }
string NewTraktAuthToken { get; set; } string NewTraktAuthToken { get; set; }
string NewTraktRefreshToken {get; set; } string NewTraktRefreshToken {get; set; }
int NewTraktTokenExpiry { get; set; } int NewTraktTokenExpiry { get; set; }
//UI //UI
int FirstDayOfWeek { get; set; } int FirstDayOfWeek { get; set; }
@@ -0,0 +1,16 @@
using System.Data;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(136)]
public class add_pathstate_to_movies : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Movies").AddColumn("PathState").AsInt32().WithDefaultValue(2);
}
}
}
@@ -0,0 +1,60 @@
using System.Data;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
using System.Text;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text.RegularExpressions;
using System.Globalization;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(137)]
public class add_import_exclusions_table : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
if (!this.Schema.Schema("dbo").Table("ImportExclusions").Exists())
{
Create.TableForModel("ImportExclusions")
.WithColumn("TmdbId").AsInt64().NotNullable().Unique().PrimaryKey()
.WithColumn("MovieTitle").AsString().Nullable()
.WithColumn("MovieYear").AsInt64().Nullable().WithDefault(0);
}
Execute.WithConnection(AddExisting);
}
private void AddExisting(IDbConnection conn, IDbTransaction tran)
{
using (IDbCommand getSeriesCmd = conn.CreateCommand())
{
getSeriesCmd.Transaction = tran;
getSeriesCmd.CommandText = @"SELECT Key, Value FROM Config WHERE Key = 'importexclusions'";
TextInfo textInfo = new CultureInfo("en-US", false).TextInfo;
using (IDataReader seriesReader = getSeriesCmd.ExecuteReader())
{
while (seriesReader.Read())
{
var Key = seriesReader.GetString(0);
var Value = seriesReader.GetString(1);
var importExclusions = Value.Split(',').Select(x => {
return string.Format("(\"{0}\", \"{1}\")", Regex.Replace(x, @"^.*\-(.*)$", "$1"),
textInfo.ToTitleCase(string.Join(" ", x.Split('-').DropLast(1))));
}).ToList();
using (IDbCommand updateCmd = conn.CreateCommand())
{
updateCmd.Transaction = tran;
updateCmd.CommandText = "INSERT INTO ImportExclusions (tmdbid, MovieTitle) VALUES " + string.Join(", ", importExclusions);
updateCmd.ExecuteNonQuery();
}
}
}
}
}
}
}
@@ -35,6 +35,7 @@ using NzbDrone.Core.Extras.Others;
using NzbDrone.Core.Extras.Subtitles; using NzbDrone.Core.Extras.Subtitles;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.NetImport; using NzbDrone.Core.NetImport;
using NzbDrone.Core.NetImport.ImportExclusions;
namespace NzbDrone.Core.Datastore namespace NzbDrone.Core.Datastore
{ {
@@ -105,6 +106,8 @@ namespace NzbDrone.Core.Datastore
.Relationship() .Relationship()
.HasOne(s => s.Profile, s => s.ProfileId) .HasOne(s => s.Profile, s => s.ProfileId)
.HasOne(m => m.MovieFile, m => m.MovieFileId); .HasOne(m => m.MovieFile, m => m.MovieFileId);
Mapper.Entity<ImportExclusion>().RegisterModel("ImportExclusions");
Mapper.Entity<Episode>().RegisterModel("Episodes") Mapper.Entity<Episode>().RegisterModel("Episodes")
@@ -4,18 +4,21 @@ using System.Linq;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Delay; using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.DecisionEngine namespace NzbDrone.Core.DecisionEngine
{ {
public class DownloadDecisionComparer : IComparer<DownloadDecision> public class DownloadDecisionComparer : IComparer<DownloadDecision>
{ {
private readonly IDelayProfileService _delayProfileService; private readonly IDelayProfileService _delayProfileService;
private readonly IConfigService _configService;
public delegate int CompareDelegate(DownloadDecision x, DownloadDecision y); public delegate int CompareDelegate(DownloadDecision x, DownloadDecision y);
public delegate int CompareDelegate<TSubject, TValue>(DownloadDecision x, DownloadDecision y); public delegate int CompareDelegate<TSubject, TValue>(DownloadDecision x, DownloadDecision y);
public DownloadDecisionComparer(IDelayProfileService delayProfileService) public DownloadDecisionComparer(IDelayProfileService delayProfileService, IConfigService configService)
{ {
_delayProfileService = delayProfileService; _delayProfileService = delayProfileService;
_configService = configService;
} }
public int Compare(DownloadDecision x, DownloadDecision y) public int Compare(DownloadDecision x, DownloadDecision y)
@@ -24,6 +27,7 @@ namespace NzbDrone.Core.DecisionEngine
{ {
CompareQuality, CompareQuality,
ComparePreferredWords, ComparePreferredWords,
CompareIndexerFlags,
CompareProtocol, CompareProtocol,
ComparePeersIfTorrent, ComparePeersIfTorrent,
CompareAgeIfUsenet, CompareAgeIfUsenet,
@@ -84,7 +88,22 @@ namespace NzbDrone.Core.DecisionEngine
return num; return num;
}); });
; } }
private int CompareIndexerFlags(DownloadDecision x, DownloadDecision y)
{
var releaseX = x.RemoteMovie.Release;
var releaseY = y.RemoteMovie.Release;
if (_configService.PreferIndexerFlags)
{
return CompareBy(x.RemoteMovie.Release, y.RemoteMovie.Release, release => ScoreFlags(release.IndexerFlags));
}
else
{
return 0;
}
}
private int CompareProtocol(DownloadDecision x, DownloadDecision y) private int CompareProtocol(DownloadDecision x, DownloadDecision y)
{ {
@@ -185,5 +204,34 @@ namespace NzbDrone.Core.DecisionEngine
return CompareBy(x.RemoteMovie, y.RemoteMovie, remoteEpisode => remoteEpisode.Release.Size.Round(200.Megabytes())); return CompareBy(x.RemoteMovie, y.RemoteMovie, remoteEpisode => remoteEpisode.Release.Size.Round(200.Megabytes()));
} }
private int ScoreFlags(IndexerFlags flags)
{
var flagValues = Enum.GetValues(typeof(IndexerFlags));
var score = 0;
foreach (IndexerFlags value in flagValues)
{
if ((flags & value) == value)
{
switch (value)
{
case IndexerFlags.G_DoubleUpload:
case IndexerFlags.G_Freeleech:
case IndexerFlags.PTP_Approved:
case IndexerFlags.PTP_Golden:
case IndexerFlags.HDB_Internal:
score += 2;
break;
case IndexerFlags.G_Halfleech:
score += 1;
break;
}
}
}
return score;
}
} }
} }
@@ -1,6 +1,7 @@
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Core.Profiles.Delay; using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.DecisionEngine namespace NzbDrone.Core.DecisionEngine
{ {
@@ -13,10 +14,12 @@ namespace NzbDrone.Core.DecisionEngine
public class DownloadDecisionPriorizationService : IPrioritizeDownloadDecision public class DownloadDecisionPriorizationService : IPrioritizeDownloadDecision
{ {
private readonly IDelayProfileService _delayProfileService; private readonly IDelayProfileService _delayProfileService;
private readonly IConfigService _configService;
public DownloadDecisionPriorizationService(IDelayProfileService delayProfileService) public DownloadDecisionPriorizationService(IDelayProfileService delayProfileService, IConfigService configService)
{ {
_delayProfileService = delayProfileService; _delayProfileService = delayProfileService;
_configService = configService;
} }
public List<DownloadDecision> PrioritizeDecisions(List<DownloadDecision> decisions) public List<DownloadDecision> PrioritizeDecisions(List<DownloadDecision> decisions)
@@ -24,7 +27,7 @@ namespace NzbDrone.Core.DecisionEngine
return decisions.Where(c => c.RemoteEpisode.Series != null) return decisions.Where(c => c.RemoteEpisode.Series != null)
.GroupBy(c => c.RemoteEpisode.Series.Id, (seriesId, downloadDecisions) => .GroupBy(c => c.RemoteEpisode.Series.Id, (seriesId, downloadDecisions) =>
{ {
return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_delayProfileService)); return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_delayProfileService, _configService));
}) })
.SelectMany(c => c) .SelectMany(c => c)
.Union(decisions.Where(c => c.RemoteEpisode.Series == null)) .Union(decisions.Where(c => c.RemoteEpisode.Series == null))
@@ -36,7 +39,7 @@ namespace NzbDrone.Core.DecisionEngine
return decisions.Where(c => c.RemoteMovie.Movie != null) return decisions.Where(c => c.RemoteMovie.Movie != null)
.GroupBy(c => c.RemoteMovie.Movie.Id, (movieId, downloadDecisions) => .GroupBy(c => c.RemoteMovie.Movie.Id, (movieId, downloadDecisions) =>
{ {
return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_delayProfileService)); return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_delayProfileService, _configService));
}) })
.SelectMany(c => c) .SelectMany(c => c)
.Union(decisions.Where(c => c.RemoteMovie.Movie == null)) .Union(decisions.Where(c => c.RemoteMovie.Movie == null))
@@ -1,4 +1,4 @@
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
@@ -121,6 +121,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
} }
var qualityDefinition = _qualityDefinitionService.Get(quality); var qualityDefinition = _qualityDefinitionService.Get(quality);
if (subject.Movie.Runtime == 0)
{
_logger.Info("{0} has no runtime information using median movie runtime of 110 minutes.", subject.Movie);
subject.Movie.Runtime = 110;
}
if (qualityDefinition.MinSize.HasValue) if (qualityDefinition.MinSize.HasValue)
{ {
var minSize = qualityDefinition.MinSize.Value.Megabytes(); var minSize = qualityDefinition.MinSize.Value.Megabytes();
@@ -86,8 +86,9 @@ namespace NzbDrone.Core.Download.Clients.Transmission
item.Status = DownloadItemStatus.Warning; item.Status = DownloadItemStatus.Warning;
item.Message = torrent.ErrorString; item.Message = torrent.ErrorString;
} }
else if (torrent.Status == TransmissionTorrentStatus.Seeding || else if (torrent.LeftUntilDone == 0 && (torrent.Status == TransmissionTorrentStatus.Stopped ||
torrent.Status == TransmissionTorrentStatus.SeedingWait) torrent.Status == TransmissionTorrentStatus.Seeding ||
torrent.Status == TransmissionTorrentStatus.SeedingWait))
{ {
item.Status = DownloadItemStatus.Completed; item.Status = DownloadItemStatus.Completed;
} }
@@ -26,7 +26,19 @@ namespace NzbDrone.Core.Download.Clients.Vuze
protected override OsPath GetOutputPath(OsPath outputPath, TransmissionTorrent torrent) protected override OsPath GetOutputPath(OsPath outputPath, TransmissionTorrent torrent)
{ {
_logger.Debug("Vuze output directory: {0}", outputPath); // Vuze has similar behavior as uTorrent:
// - A multi-file torrent is downloaded in a job folder and 'outputPath' points to that directory directly.
// - A single-file torrent is downloaded in the root folder and 'outputPath' poinst to that root folder.
// We have to make sure the return value points to the job folder OR file.
if (outputPath == null || outputPath.FileName == torrent.Name)
{
_logger.Trace("Vuze output directory: {0}", outputPath);
}
else
{
outputPath = outputPath + torrent.Name;
_logger.Trace("Vuze output file: {0}", outputPath);
}
return outputPath; return outputPath;
} }
@@ -50,4 +62,4 @@ namespace NzbDrone.Core.Download.Clients.Vuze
public override string Name => "Vuze"; public override string Name => "Vuze";
} }
} }
@@ -129,7 +129,14 @@ namespace NzbDrone.Core.Download
{ {
var statusMessages = importResults var statusMessages = importResults
.Where(v => v.Result != ImportResultType.Imported) .Where(v => v.Result != ImportResultType.Imported)
.Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalMovie.Path), v.Errors)) .Select(v =>
{
if (v.ImportDecision.LocalMovie == null)
{
return new TrackedDownloadStatusMessage("", v.Errors);
}
return new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalMovie.Path), v.Errors);
})
.ToArray(); .ToArray();
trackedDownload.Warn(statusMessages); trackedDownload.Warn(statusMessages);
@@ -226,7 +226,12 @@ namespace NzbDrone.Core.Download.Pending
Title = decision.RemoteMovie.Release.Title, Title = decision.RemoteMovie.Release.Title,
Added = DateTime.UtcNow Added = DateTime.UtcNow
}; };
_repository.Insert(release); if (release.ParsedMovieInfo == null)
{
_logger.Warn("Pending release {0} does not have ParsedMovieInfo, will cause issues.", release.Title);
}
_repository.Insert(release);
_eventAggregator.PublishEvent(new PendingReleasesUpdatedEvent()); _eventAggregator.PublishEvent(new PendingReleasesUpdatedEvent());
} }
@@ -69,6 +69,12 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
{ {
var id = torrent.Id; var id = torrent.Id;
var title = $"{torrent.Name}.{torrent.Year}.{torrent.Resolution}.{torrent.Media}.{torrent.Encoding}.{torrent.AudioFormat}-{torrent.ReleaseGroup}"; var title = $"{torrent.Name}.{torrent.Year}.{torrent.Resolution}.{torrent.Media}.{torrent.Encoding}.{torrent.AudioFormat}-{torrent.ReleaseGroup}";
IndexerFlags flags = 0;
if (torrent.Freeleech == "1.00")
{
flags |= IndexerFlags.G_Freeleech;
}
torrentInfos.Add(new TorrentInfo() torrentInfos.Add(new TorrentInfo()
{ {
@@ -80,7 +86,8 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
Seeders = int.Parse(torrent.Seeders), Seeders = int.Parse(torrent.Seeders),
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders), Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
PublishDate = torrent.Time.ToUniversalTime(), PublishDate = torrent.Time.ToUniversalTime(),
ImdbId = int.Parse(torrent.ImdbId.Substring(2)) ImdbId = int.Parse(torrent.ImdbId.Substring(2)),
IndexerFlags = flags,
}); });
} }
} }
@@ -53,6 +53,18 @@ namespace NzbDrone.Core.Indexers.HDBits
var id = result.Id; var id = result.Id;
var internalRelease = (result.TypeOrigin == 1 ? true : false); var internalRelease = (result.TypeOrigin == 1 ? true : false);
IndexerFlags flags = 0;
if (result.FreeLeech == "yes")
{
flags |= IndexerFlags.G_Freeleech;
}
if (internalRelease)
{
flags |= IndexerFlags.HDB_Internal;
}
torrentInfos.Add(new HDBitsInfo() torrentInfos.Add(new HDBitsInfo()
{ {
Guid = string.Format("HDBits-{0}", id), Guid = string.Format("HDBits-{0}", id),
@@ -65,7 +77,8 @@ namespace NzbDrone.Core.Indexers.HDBits
Peers = result.Leechers + result.Seeders, Peers = result.Leechers + result.Seeders,
PublishDate = result.Added.ToUniversalTime(), PublishDate = result.Added.ToUniversalTime(),
Internal = internalRelease, Internal = internalRelease,
ImdbId = result.ImdbInfo?.Id ?? 0 ImdbId = result.ImdbInfo?.Id ?? 0,
IndexerFlags = flags
}); });
} }
@@ -28,6 +28,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public string ReleaseName { get; set; } public string ReleaseName { get; set; }
public bool Checked { get; set; } public bool Checked { get; set; }
public bool GoldenPopcorn { get; set; } public bool GoldenPopcorn { get; set; }
public string FreeleechType { get; set; }
} }
public class Movie public class Movie
@@ -55,60 +55,69 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
{ {
var id = torrent.Id; var id = torrent.Id;
var title = torrent.ReleaseName; var title = torrent.ReleaseName;
IndexerFlags flags = 0;
if (torrent.GoldenPopcorn) if (torrent.GoldenPopcorn)
{ {
title = $"{title} 🍿"; flags |= IndexerFlags.PTP_Golden;//title = $"{title} 🍿";
} }
if (torrent.Checked) if (torrent.Checked)
{ {
title = $"{title} ✔"; flags |= IndexerFlags.PTP_Approved;//title = $"{title} ✔";
}
if (torrent.FreeleechType == "Freeleech")
{
flags |= IndexerFlags.G_Freeleech;
} }
// Only add approved torrents // Only add approved torrents
if (_settings.RequireApproved && torrent.Checked) if (_settings.RequireApproved && torrent.Checked)
{
torrentInfos.Add(new PassThePopcornInfo()
{ {
Guid = string.Format("PassThePopcorn-{0}", id), torrentInfos.Add(new PassThePopcornInfo()
Title = title, {
Size = long.Parse(torrent.Size), Guid = string.Format("PassThePopcorn-{0}", id),
DownloadUrl = GetDownloadUrl(id, jsonResponse.AuthKey, jsonResponse.PassKey), Title = title,
InfoUrl = GetInfoUrl(result.GroupId, id), Size = long.Parse(torrent.Size),
Seeders = int.Parse(torrent.Seeders), DownloadUrl = GetDownloadUrl(id, jsonResponse.AuthKey, jsonResponse.PassKey),
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders), InfoUrl = GetInfoUrl(result.GroupId, id),
PublishDate = torrent.UploadTime.ToUniversalTime(), Seeders = int.Parse(torrent.Seeders),
Golden = torrent.GoldenPopcorn, Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
Scene = torrent.Scene, PublishDate = torrent.UploadTime.ToUniversalTime(),
Approved = torrent.Checked, Golden = torrent.GoldenPopcorn,
ImdbId = (result.ImdbId.IsNotNullOrWhiteSpace() ? int.Parse(result.ImdbId) : 0) Scene = torrent.Scene,
}); Approved = torrent.Checked,
} ImdbId = (result.ImdbId.IsNotNullOrWhiteSpace() ? int.Parse(result.ImdbId) : 0),
// Add all torrents IndexerFlags = flags
else if (!_settings.RequireApproved) });
{ }
torrentInfos.Add(new PassThePopcornInfo()
// Add all torrents
else if (!_settings.RequireApproved)
{ {
Guid = string.Format("PassThePopcorn-{0}", id), torrentInfos.Add(new PassThePopcornInfo()
Title = title, {
Size = long.Parse(torrent.Size), Guid = string.Format("PassThePopcorn-{0}", id),
DownloadUrl = GetDownloadUrl(id, jsonResponse.AuthKey, jsonResponse.PassKey), Title = title,
InfoUrl = GetInfoUrl(result.GroupId, id), Size = long.Parse(torrent.Size),
Seeders = int.Parse(torrent.Seeders), DownloadUrl = GetDownloadUrl(id, jsonResponse.AuthKey, jsonResponse.PassKey),
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders), InfoUrl = GetInfoUrl(result.GroupId, id),
PublishDate = torrent.UploadTime.ToUniversalTime(), Seeders = int.Parse(torrent.Seeders),
Golden = torrent.GoldenPopcorn, Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
Scene = torrent.Scene, PublishDate = torrent.UploadTime.ToUniversalTime(),
Approved = torrent.Checked, Golden = torrent.GoldenPopcorn,
ImdbId = (result.ImdbId.IsNotNullOrWhiteSpace() ? int.Parse(result.ImdbId) : 0) Scene = torrent.Scene,
}); Approved = torrent.Checked,
} ImdbId = (result.ImdbId.IsNotNullOrWhiteSpace() ? int.Parse(result.ImdbId) : 0),
// Don't add any torrents IndexerFlags = flags
else if (_settings.RequireApproved && !torrent.Checked) });
{ }
continue; // Don't add any torrents
} else if (_settings.RequireApproved && !torrent.Checked)
{
continue;
}
} }
} }
@@ -46,6 +46,9 @@ namespace NzbDrone.Core.Indexers.Torznab
torrentInfo.ImdbId = int.Parse(GetImdbId(item).Substring(2)); torrentInfo.ImdbId = int.Parse(GetImdbId(item).Substring(2));
} }
} }
torrentInfo.IndexerFlags = GetFlags(item);
return torrentInfo; return torrentInfo;
} }
@@ -151,6 +154,32 @@ namespace NzbDrone.Core.Indexers.Torznab
return base.GetPeers(item); return base.GetPeers(item);
} }
protected IndexerFlags GetFlags(XElement item)
{
IndexerFlags flags = 0;
var downloadFactor = TryGetFloatTorznabAttribute(item, "downloadvolumefactor", 1);
var uploadFactor = TryGetFloatTorznabAttribute(item, "uploadvolumefactor", 1);
if (uploadFactor == 2)
{
flags |= IndexerFlags.G_DoubleUpload;
}
if (downloadFactor == 0.5)
{
flags |= IndexerFlags.G_Halfleech;
}
if (downloadFactor == 0.0)
{
flags |= IndexerFlags.G_Freeleech;
}
return flags;
}
protected string TryGetTorznabAttribute(XElement item, string key, string defaultValue = "") protected string TryGetTorznabAttribute(XElement item, string key, string defaultValue = "")
{ {
var attr = item.Elements(ns + "attr").FirstOrDefault(e => e.Attribute("name").Value.Equals(key, StringComparison.CurrentCultureIgnoreCase)); var attr = item.Elements(ns + "attr").FirstOrDefault(e => e.Attribute("name").Value.Equals(key, StringComparison.CurrentCultureIgnoreCase));
@@ -160,6 +189,20 @@ namespace NzbDrone.Core.Indexers.Torznab
return attr.Attribute("value").Value; return attr.Attribute("value").Value;
} }
return defaultValue;
}
protected float TryGetFloatTorznabAttribute(XElement item, string key, float defaultValue = 0)
{
var attr = TryGetTorznabAttribute(item, key, defaultValue.ToString());
float result = 0;
if (float.TryParse(attr, out result))
{
return result;
}
return defaultValue; return defaultValue;
} }
} }
+2 -2
View File
@@ -134,9 +134,9 @@ namespace NzbDrone.Core.Jobs
{ {
var interval = _configService.RssSyncInterval; var interval = _configService.RssSyncInterval;
if (interval > 0 && interval < 10) if (interval > 0 && interval < 5)
{ {
return 10; return 5;
} }
if (interval < 0) if (interval < 0)
@@ -0,0 +1,20 @@
using NzbDrone.Core.Messaging.Commands;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.MediaFiles.Commands
{
public class RenameMovieFolderCommand : Command
{
public List<int> MovieIds { get; set; }
public override bool SendUpdatesToClient => true;
public RenameMovieFolderCommand(List<int> ids)
{
MovieIds = ids;
}
}
}
@@ -45,6 +45,7 @@ namespace NzbDrone.Core.MediaFiles
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly IMovieService _movieService; private readonly IMovieService _movieService;
private readonly IMovieFileRepository _movieFileRepository; private readonly IMovieFileRepository _movieFileRepository;
private readonly IRenameMovieFileService _renameMovieFiles;
private readonly Logger _logger; private readonly Logger _logger;
public DiskScanService(IDiskProvider diskProvider, public DiskScanService(IDiskProvider diskProvider,
@@ -57,6 +58,7 @@ namespace NzbDrone.Core.MediaFiles
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
IMovieService movieService, IMovieService movieService,
IMovieFileRepository movieFileRepository, IMovieFileRepository movieFileRepository,
IRenameMovieFileService renameMovieFiles,
Logger logger) Logger logger)
{ {
_diskProvider = diskProvider; _diskProvider = diskProvider;
@@ -69,6 +71,7 @@ namespace NzbDrone.Core.MediaFiles
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_movieService = movieService; _movieService = movieService;
_movieFileRepository = movieFileRepository; _movieFileRepository = movieFileRepository;
_renameMovieFiles = renameMovieFiles;
_logger = logger; _logger = logger;
} }
@@ -135,6 +138,9 @@ namespace NzbDrone.Core.MediaFiles
public void Scan(Movie movie) public void Scan(Movie movie)
{ {
//Try renaming the movie path in case anything changed such as year, title or something else.
_renameMovieFiles.RenameMoviePath(movie, true);
var rootFolder = _diskProvider.GetParentFolder(movie.Path); var rootFolder = _diskProvider.GetParentFolder(movie.Path);
if (!_diskProvider.FolderExists(rootFolder)) if (!_diskProvider.FolderExists(rootFolder))
@@ -0,0 +1,18 @@
using NzbDrone.Common.Messaging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.MediaFiles.Events
{
public class MovieFileUpdatedEvent : IEvent
{
public MovieFile MovieFile { get; private set; }
public MovieFileUpdatedEvent(MovieFile movieFile)
{
MovieFile = movieFile;
}
}
}
@@ -30,6 +30,7 @@ namespace NzbDrone.Core.MediaFiles
private readonly IDiskTransferService _diskTransferService; private readonly IDiskTransferService _diskTransferService;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IMediaFileAttributeService _mediaFileAttributeService; private readonly IMediaFileAttributeService _mediaFileAttributeService;
private readonly IRecycleBinProvider _recycleBinProvider;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly IConfigService _configService; private readonly IConfigService _configService;
private readonly Logger _logger; private readonly Logger _logger;
@@ -40,6 +41,7 @@ namespace NzbDrone.Core.MediaFiles
IDiskTransferService diskTransferService, IDiskTransferService diskTransferService,
IDiskProvider diskProvider, IDiskProvider diskProvider,
IMediaFileAttributeService mediaFileAttributeService, IMediaFileAttributeService mediaFileAttributeService,
IRecycleBinProvider recycleBinProvider,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
IConfigService configService, IConfigService configService,
Logger logger) Logger logger)
@@ -50,6 +52,7 @@ namespace NzbDrone.Core.MediaFiles
_diskTransferService = diskTransferService; _diskTransferService = diskTransferService;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_mediaFileAttributeService = mediaFileAttributeService; _mediaFileAttributeService = mediaFileAttributeService;
_recycleBinProvider = recycleBinProvider;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_configService = configService; _configService = configService;
_logger = logger; _logger = logger;
@@ -116,6 +119,15 @@ namespace NzbDrone.Core.MediaFiles
_diskTransferService.TransferFile(movieFilePath, destinationFilePath, mode); _diskTransferService.TransferFile(movieFilePath, destinationFilePath, mode);
var oldMoviePath = movie.Path;
var newMoviePath = new OsPath(destinationFilePath).Directory.FullPath.TrimEnd(Path.DirectorySeparatorChar);
movie.Path = newMoviePath;
if (oldMoviePath != newMoviePath)
{
_movieService.UpdateMovie(movie);
}
movieFile.RelativePath = movie.Path.GetRelativePath(destinationFilePath); movieFile.RelativePath = movie.Path.GetRelativePath(destinationFilePath);
_updateMovieFileService.ChangeFileDateForFile(movieFile, movie); _updateMovieFileService.ChangeFileDateForFile(movieFile, movie);
@@ -132,6 +144,14 @@ namespace NzbDrone.Core.MediaFiles
_mediaFileAttributeService.SetFilePermissions(destinationFilePath); _mediaFileAttributeService.SetFilePermissions(destinationFilePath);
if(oldMoviePath != newMoviePath)
{
if (_diskProvider.GetFiles(oldMoviePath, SearchOption.AllDirectories).Count() == 0)
{
_recycleBinProvider.DeleteFolder(oldMoviePath);
}
}
return movieFile; return movieFile;
} }
@@ -143,7 +163,9 @@ namespace NzbDrone.Core.MediaFiles
private void EnsureMovieFolder(MovieFile movieFile, Movie movie, string filePath) private void EnsureMovieFolder(MovieFile movieFile, Movie movie, string filePath)
{ {
var movieFolder = Path.GetDirectoryName(filePath); var movieFolder = Path.GetDirectoryName(filePath);
movie.Path = movieFolder;
var rootFolder = new OsPath(movieFolder).Directory.FullPath; var rootFolder = new OsPath(movieFolder).Directory.FullPath;
var fileName = Path.GetFileName(filePath);
if (!_diskProvider.FolderExists(rootFolder)) if (!_diskProvider.FolderExists(rootFolder))
{ {
@@ -156,7 +178,7 @@ namespace NzbDrone.Core.MediaFiles
if (!_diskProvider.FolderExists(movieFolder)) if (!_diskProvider.FolderExists(movieFolder))
{ {
CreateFolder(movieFolder); CreateFolder(movieFolder);
newEvent.SeriesFolder = movieFolder; newEvent.MovieFolder = movieFolder;
changed = true; changed = true;
} }
@@ -13,23 +13,29 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Organizer; using NzbDrone.Core.Organizer;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.MediaFiles namespace NzbDrone.Core.MediaFiles
{ {
public interface IRenameMovieFileService public interface IRenameMovieFileService
{ {
List<RenameMovieFilePreview> GetRenamePreviews(int movieId); List<RenameMovieFilePreview> GetRenamePreviews(int movieId);
void RenameMoviePath(Movie movie, bool shouldRenameFiles);
} }
public class RenameMovieFileService : IRenameMovieFileService, public class RenameMovieFileService : IRenameMovieFileService,
IExecute<RenameMovieFilesCommand>, IExecute<RenameMovieFilesCommand>,
IExecute<RenameMovieCommand> IExecute<RenameMovieCommand>,
IExecute<RenameMovieFolderCommand>
{ {
private readonly IMovieService _movieService; private readonly IMovieService _movieService;
private readonly IMediaFileService _mediaFileService; private readonly IMediaFileService _mediaFileService;
private readonly IMoveMovieFiles _movieFileMover; private readonly IMoveMovieFiles _movieFileMover;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly IBuildFileNames _filenameBuilder; private readonly IBuildFileNames _filenameBuilder;
private readonly IConfigService _configService;
private readonly IDiskProvider _diskProvider;
private readonly IRecycleBinProvider _recycleBinProvider;
private readonly Logger _logger; private readonly Logger _logger;
public RenameMovieFileService(IMovieService movieService, public RenameMovieFileService(IMovieService movieService,
@@ -37,6 +43,9 @@ namespace NzbDrone.Core.MediaFiles
IMoveMovieFiles movieFileMover, IMoveMovieFiles movieFileMover,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
IBuildFileNames filenameBuilder, IBuildFileNames filenameBuilder,
IConfigService configService,
IRecycleBinProvider recycleBinProvider,
IDiskProvider diskProvider,
Logger logger) Logger logger)
{ {
_movieService = movieService; _movieService = movieService;
@@ -44,6 +53,9 @@ namespace NzbDrone.Core.MediaFiles
_movieFileMover = movieFileMover; _movieFileMover = movieFileMover;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_filenameBuilder = filenameBuilder; _filenameBuilder = filenameBuilder;
_configService = configService;
_recycleBinProvider = recycleBinProvider;
_diskProvider = diskProvider;
_logger = logger; _logger = logger;
} }
@@ -71,8 +83,9 @@ namespace NzbDrone.Core.MediaFiles
{ {
MovieId = movie.Id, MovieId = movie.Id,
MovieFileId = file.Id, MovieFileId = file.Id,
ExistingPath = file.RelativePath, ExistingPath = movieFilePath,
NewPath = movie.Path.GetRelativePath(newPath) //NewPath = movie.Path.GetRelativePath(newPath)
NewPath = newPath
}; };
} }
@@ -80,13 +93,19 @@ namespace NzbDrone.Core.MediaFiles
} }
private void RenameFiles(List<MovieFile> movieFiles, Movie movie) private void RenameFiles(List<MovieFile> movieFiles, Movie movie, string oldMoviePath = null)
{ {
var renamed = new List<MovieFile>(); var renamed = new List<MovieFile>();
foreach(var movieFile in movieFiles) if (oldMoviePath == null)
{ {
var movieFilePath = Path.Combine(movie.Path, movieFile.RelativePath); oldMoviePath = movie.Path;
}
foreach (var movieFile in movieFiles)
{
var oldMovieFilePath = Path.Combine(oldMoviePath, movieFile.RelativePath);
movieFile.Path = oldMovieFilePath;
try try
{ {
@@ -94,22 +113,64 @@ namespace NzbDrone.Core.MediaFiles
_movieFileMover.MoveMovieFile(movieFile, movie); _movieFileMover.MoveMovieFile(movieFile, movie);
_mediaFileService.Update(movieFile); _mediaFileService.Update(movieFile);
_movieService.UpdateMovie(movie);
renamed.Add(movieFile); renamed.Add(movieFile);
_logger.Debug("Renamed movie file: {0}", movieFile); _logger.Debug("Renamed movie file: {0}", movieFile);
} }
catch(SameFilenameException ex) catch (SameFilenameException ex)
{ {
_logger.Debug("File not renamed, source and destination are the same: {0}", ex.Filename); _logger.Debug("File not renamed, source and destination are the same: {0}", ex.Filename);
} }
catch(Exception ex) catch (Exception ex)
{ {
_logger.Error(ex, "Failed to rename file: " + movieFilePath); _logger.Error(ex, "Failed to rename file: " + oldMovieFilePath);
} }
} }
} }
public void RenameMoviePath(Movie movie, bool shouldRenameFiles = true)
{
var newFolder = _filenameBuilder.BuildMoviePath(movie);
if (newFolder != movie.Path && movie.PathState == MoviePathState.Dynamic)
{
if (!_configService.AutoRenameFolders)
{
_logger.Info("{0}'s movie should be {1} according to your naming config.", movie, newFolder);
return;
}
_logger.Info("{0}'s movie folder changed to: {1}", movie, newFolder);
var oldFolder = movie.Path;
movie.Path = newFolder;
if (shouldRenameFiles)
{
var movieFiles = _mediaFileService.GetFilesByMovie(movie.Id);
_logger.ProgressInfo("Renaming movie files for {0}", movie.Title);
RenameFiles(movieFiles, movie, oldFolder);
_logger.ProgressInfo("All movie files renamed for {0}", movie.Title);
}
_movieService.UpdateMovie(movie);
if (_diskProvider.GetFiles(oldFolder, SearchOption.AllDirectories).Count() == 0)
{
_recycleBinProvider.DeleteFolder(oldFolder);
}
}
if (movie.PathState == MoviePathState.StaticOnce)
{
movie.PathState = MoviePathState.Dynamic;
_movieService.UpdateMovie(movie);
}
}
public void Execute(RenameMovieFilesCommand message) public void Execute(RenameMovieFilesCommand message)
{ {
var movie = _movieService.GetMovie(message.MovieId); var movie = _movieService.GetMovie(message.MovieId);
@@ -134,5 +195,17 @@ namespace NzbDrone.Core.MediaFiles
} }
} }
public void Execute(RenameMovieFolderCommand message)
{
_logger.Debug("Renaming movie folder for selected movie if necessary");
var moviesToRename = _movieService.GetMovies(message.MovieIds);
foreach(var movie in moviesToRename)
{
var movieFiles = _mediaFileService.GetFilesByMovie(movie.Id);
_logger.ProgressInfo("Renaming movie folder for {0}", movie.Title);
RenameMoviePath(movie);
}
}
} }
} }
@@ -18,6 +18,7 @@ namespace NzbDrone.Core.MediaFiles
private readonly IMediaFileService _mediaFileService; private readonly IMediaFileService _mediaFileService;
private readonly IMoveEpisodeFiles _episodeFileMover; private readonly IMoveEpisodeFiles _episodeFileMover;
private readonly IMoveMovieFiles _movieFileMover; private readonly IMoveMovieFiles _movieFileMover;
private readonly IRenameMovieFileService _movieFileRenamer;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly Logger _logger; private readonly Logger _logger;
@@ -26,6 +27,7 @@ namespace NzbDrone.Core.MediaFiles
IMoveEpisodeFiles episodeFileMover, IMoveEpisodeFiles episodeFileMover,
IMoveMovieFiles movieFileMover, IMoveMovieFiles movieFileMover,
IDiskProvider diskProvider, IDiskProvider diskProvider,
IRenameMovieFileService movieFileRenamer,
Logger logger) Logger logger)
{ {
_recycleBinProvider = recycleBinProvider; _recycleBinProvider = recycleBinProvider;
@@ -33,6 +35,7 @@ namespace NzbDrone.Core.MediaFiles
_episodeFileMover = episodeFileMover; _episodeFileMover = episodeFileMover;
_movieFileMover = movieFileMover; _movieFileMover = movieFileMover;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_movieFileRenamer = movieFileRenamer;
_logger = logger; _logger = logger;
} }
@@ -57,6 +60,10 @@ namespace NzbDrone.Core.MediaFiles
_mediaFileService.Delete(existingFile, DeleteMediaFileReason.Upgrade); _mediaFileService.Delete(existingFile, DeleteMediaFileReason.Upgrade);
} }
//Temporary for correctly getting path
localMovie.Movie.MovieFileId = 1;
localMovie.Movie.MovieFile = movieFile;
if (copyOnly) if (copyOnly)
{ {
moveFileResult.MovieFile = _movieFileMover.CopyMovieFile(movieFile, localMovie); moveFileResult.MovieFile = _movieFileMover.CopyMovieFile(movieFile, localMovie);
@@ -66,6 +73,8 @@ namespace NzbDrone.Core.MediaFiles
moveFileResult.MovieFile = _movieFileMover.MoveMovieFile(movieFile, localMovie); moveFileResult.MovieFile = _movieFileMover.MoveMovieFile(movieFile, localMovie);
} }
//_movieFileRenamer.RenameMoviePath(localMovie.Movie, false);
return moveFileResult; return moveFileResult;
} }
@@ -0,0 +1,10 @@
using System.Collections.Generic;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.MetadataSource
{
public interface IDiscoverNewMovies
{
List<Movie> DiscoverNewMovies(string action);
}
}
@@ -62,6 +62,7 @@ namespace NzbDrone.Core.MetadataSource.PreDB
private List<PreDBResult> GetResults(string category = "", string search = "") private List<PreDBResult> GetResults(string category = "", string search = "")
{ {
return new List<PreDBResult>();
var builder = new HttpRequestBuilder("http://predb.me").AddQueryParam("rss", "1"); var builder = new HttpRequestBuilder("http://predb.me").AddQueryParam("rss", "1");
if (category.IsNotNullOrWhiteSpace()) if (category.IsNotNullOrWhiteSpace())
{ {
@@ -171,10 +172,12 @@ namespace NzbDrone.Core.MetadataSource.PreDB
public bool HasReleases(Movie movie) public bool HasReleases(Movie movie)
{ {
var results = GetResults("movies", movie.Title); try
{
var results = GetResults("movies", movie.Title);
foreach (PreDBResult result in results) foreach (PreDBResult result in results)
{ {
var parsed = Parser.Parser.ParseMovieTitle(result.Title); var parsed = Parser.Parser.ParseMovieTitle(result.Title);
if (parsed == null) if (parsed == null)
{ {
@@ -182,13 +185,20 @@ namespace NzbDrone.Core.MetadataSource.PreDB
} }
var match = _parsingService.Map(parsed, "", new MovieSearchCriteria { Movie = movie }); var match = _parsingService.Map(parsed, "", new MovieSearchCriteria { Movie = movie });
if (match != null && match.Movie != null && match.Movie.Id == movie.Id) if (match != null && match.Movie != null && match.Movie.Id == movie.Id)
{ {
return true; return true;
} }
} }
return false; return false;
}
catch (Exception ex)
{
_logger.Warn(ex, "Error while looking on predb.me.");
return false;
}
} }
} }
} }
@@ -34,6 +34,8 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
public int vote_count { get; set; } public int vote_count { get; set; }
public bool video { get; set; } public bool video { get; set; }
public float vote_average { get; set; } public float vote_average { get; set; }
public string trailer_key { get; set; }
public string trailer_site { get; set; }
} }
@@ -15,10 +15,12 @@ using NzbDrone.Core.Tv;
using System.Threading; using System.Threading;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Profiles; using NzbDrone.Core.Profiles;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.NetImport.ImportExclusions;
namespace NzbDrone.Core.MetadataSource.SkyHook namespace NzbDrone.Core.MetadataSource.SkyHook
{ {
public class SkyHookProxy : IProvideSeriesInfo, ISearchForNewSeries, IProvideMovieInfo, ISearchForNewMovie public class SkyHookProxy : IProvideSeriesInfo, ISearchForNewSeries, IProvideMovieInfo, ISearchForNewMovie, IDiscoverNewMovies
{ {
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly Logger _logger; private readonly Logger _logger;
@@ -28,8 +30,10 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
private readonly ITmdbConfigService _configService; private readonly ITmdbConfigService _configService;
private readonly IMovieService _movieService; private readonly IMovieService _movieService;
private readonly IPreDBService _predbService; private readonly IPreDBService _predbService;
private readonly IImportExclusionsService _exclusionService;
public SkyHookProxy(IHttpClient httpClient, ISonarrCloudRequestBuilder requestBuilder, ITmdbConfigService configService, IMovieService movieService, IPreDBService predbService, Logger logger) public SkyHookProxy(IHttpClient httpClient, ISonarrCloudRequestBuilder requestBuilder, ITmdbConfigService configService, IMovieService movieService,
IPreDBService predbService, IImportExclusionsService exclusionService, Logger logger)
{ {
_httpClient = httpClient; _httpClient = httpClient;
_requestBuilder = requestBuilder.SkyHookTvdb; _requestBuilder = requestBuilder.SkyHookTvdb;
@@ -37,6 +41,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
_configService = configService; _configService = configService;
_movieService = movieService; _movieService = movieService;
_predbService = predbService; _predbService = predbService;
_exclusionService = exclusionService;
_logger = logger; _logger = logger;
} }
@@ -348,6 +353,64 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
return resources.movie_results.SelectList(MapMovie).FirstOrDefault(); return resources.movie_results.SelectList(MapMovie).FirstOrDefault();
} }
public List<Movie> DiscoverNewMovies(string action)
{
var allMovies = _movieService.GetAllMovies();
var allExclusions = _exclusionService.GetAllExclusions();
string allIds = string.Join(",", allMovies.Select(m => m.TmdbId));
string ignoredIds = string.Join(",", allExclusions.Select(ex => ex.TmdbId));
HttpRequest request;
List<MovieResult> results;
if (action == "upcoming")
{
var lastWeek = DateTime.Now.AddDays(-7);
var threeWeeks = DateTime.Now.AddDays(7 * 3);
request = _movieBuilder.Create().SetSegment("route", "discover")
.SetSegment("id", "movie")
.SetSegment("secondaryRoute", "")
.AddQueryParam("region", "us")
.AddQueryParam("with_release_type", "5|4|6")
.AddQueryParam("release_date.gte", lastWeek.ToString("yyyy-MM-dd"))
.AddQueryParam("sort_by", "popularity.desc")
.AddQueryParam("release_date.lte", threeWeeks.ToString("yyyy-MM-dd")).Build();
var response = _httpClient.Get<MovieSearchRoot>(request);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new HttpException(request, response);
}
results = response.Resource.results.ToList();
}
else
{
request = new HttpRequestBuilder("https://radarr.video/api/{action}/").SetSegment("action", action).Build();
request.AllowAutoRedirect = true;
request.Method = HttpMethod.POST;
request.Headers.ContentType = "application/x-www-form-urlencoded";
request.SetContent($"tmdbids={allIds}&ignoredIds={ignoredIds}");
var response = _httpClient.Post<List<MovieResult>>(request);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new HttpException(request, response);
}
results = response.Resource;
}
results = results.Where(m => allMovies.None(mo => mo.TmdbId == m.id) && allExclusions.None(ex => ex.TmdbId == m.id)).ToList();
return results.SelectList(MapMovie);
}
private string StripTrailingTheFromTitle(string title) private string StripTrailingTheFromTitle(string title)
{ {
if(title.EndsWith(",the")) if(title.EndsWith(",the"))
@@ -551,6 +614,8 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
imdbMovie.Images = new List<MediaCover.MediaCover>(); imdbMovie.Images = new List<MediaCover.MediaCover>();
imdbMovie.Overview = result.overview; imdbMovie.Overview = result.overview;
imdbMovie.Ratings = new Ratings { Value = (decimal)result.vote_average, Votes = result.vote_count};
try try
{ {
var imdbPoster = _configService.GetCoverForURL(result.poster_path, MediaCoverTypes.Poster); var imdbPoster = _configService.GetCoverForURL(result.poster_path, MediaCoverTypes.Poster);
@@ -561,6 +626,15 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
_logger.Debug(result); _logger.Debug(result);
} }
if (result.trailer_key.IsNotNullOrWhiteSpace() && result.trailer_site.IsNotNullOrWhiteSpace())
{
if (result.trailer_site == "youtube")
{
imdbMovie.YouTubeTrailerId = result.trailer_key;
}
}
return imdbMovie; return imdbMovie;
} }
catch (Exception e) catch (Exception e)
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using Marr.Data;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.MediaFiles;
using System.IO;
namespace NzbDrone.Core.NetImport.ImportExclusions
{
public class ImportExclusion : ModelBase
{
public int TmdbId { get; set; }
public string MovieTitle { get; set; }
public int MovieYear { get; set; }
new public string ToString()
{
return string.Format("Excluded Movie: [{0}][{1} {2}]", TmdbId, MovieTitle, MovieYear);
}
}
}
@@ -0,0 +1,35 @@
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 CoreParser = NzbDrone.Core.Parser.Parser;
namespace NzbDrone.Core.NetImport.ImportExclusions
{
public interface IImportExclusionsRepository : IBasicRepository<ImportExclusion>
{
bool IsMovieExcluded(int tmdbid);
}
public class ImportExclusionsRepository : BasicRepository<ImportExclusion>, IImportExclusionsRepository
{
protected IMainDatabase _database;
public ImportExclusionsRepository(IMainDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
_database = database;
}
public bool IsMovieExcluded(int tmdbid)
{
return Query.Where(ex => ex.TmdbId == tmdbid).Any();
}
}
}
@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Tv.Events;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.NetImport.ImportExclusions
{
public interface IImportExclusionsService
{
List<ImportExclusion> GetAllExclusions();
bool IsMovieExcluded(int tmdbid);
ImportExclusion AddExclusion(ImportExclusion exclusion);
void RemoveExclusion(ImportExclusion exclusion);
ImportExclusion GetById(int id);
}
public class ImportExclusionsService : IImportExclusionsService
{
private readonly IImportExclusionsRepository _exclusionRepository;
private readonly IConfigService _configService;
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
public ImportExclusionsService(IImportExclusionsRepository exclusionRepository,
IEventAggregator eventAggregator,
IConfigService configService,
Logger logger)
{
_exclusionRepository = exclusionRepository;
_eventAggregator = eventAggregator;
_configService = configService;
_logger = logger;
}
public ImportExclusion AddExclusion(ImportExclusion exclusion)
{
return _exclusionRepository.Insert(exclusion);
}
public List<ImportExclusion> GetAllExclusions()
{
return _exclusionRepository.All().ToList();
}
public bool IsMovieExcluded(int tmdbid)
{
return _exclusionRepository.IsMovieExcluded(tmdbid);
}
public void RemoveExclusion(ImportExclusion exclusion)
{
_exclusionRepository.Delete(exclusion);
}
public ImportExclusion GetById(int id)
{
return _exclusionRepository.Get(id);
}
}
}
@@ -13,6 +13,7 @@ using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.IndexerSearch; using NzbDrone.Core.IndexerSearch;
using NzbDrone.Core.NetImport.ImportExclusions;
namespace NzbDrone.Core.NetImport namespace NzbDrone.Core.NetImport
{ {
@@ -32,11 +33,14 @@ namespace NzbDrone.Core.NetImport
private readonly IConfigService _configService; private readonly IConfigService _configService;
private readonly ISearchForNzb _nzbSearchService; private readonly ISearchForNzb _nzbSearchService;
private readonly IProcessDownloadDecisions _processDownloadDecisions; private readonly IProcessDownloadDecisions _processDownloadDecisions;
private readonly IImportExclusionsService _exclusionService;
public NetImportSearchService(INetImportFactory netImportFactory, IMovieService movieService, public NetImportSearchService(INetImportFactory netImportFactory, IMovieService movieService,
ISearchForNewMovie movieSearch, IRootFolderService rootFolder, ISearchForNzb nzbSearchService, ISearchForNewMovie movieSearch, IRootFolderService rootFolder, ISearchForNzb nzbSearchService,
IProcessDownloadDecisions processDownloadDecisions, IConfigService configService, Logger logger) IProcessDownloadDecisions processDownloadDecisions, IConfigService configService,
IImportExclusionsService exclusionService,
Logger logger)
{ {
_netImportFactory = netImportFactory; _netImportFactory = netImportFactory;
_movieService = movieService; _movieService = movieService;
@@ -44,6 +48,7 @@ namespace NzbDrone.Core.NetImport
_nzbSearchService = nzbSearchService; _nzbSearchService = nzbSearchService;
_processDownloadDecisions = processDownloadDecisions; _processDownloadDecisions = processDownloadDecisions;
_rootFolder = rootFolder; _rootFolder = rootFolder;
_exclusionService = exclusionService;
_logger = logger; _logger = logger;
_configService = configService; _configService = configService;
} }
@@ -119,18 +124,12 @@ namespace NzbDrone.Core.NetImport
var importExclusions = new List<string>(); var importExclusions = new List<string>();
if (_configService.ImportExclusions != null)
{
// Replace `movie-title-tmdbid` with just tmdbid in exclusions
importExclusions = _configService.ImportExclusions.Split(',').Select(x => Regex.Replace(x, @"^.*\-(.*)$", "$1")).ToList();
// listedMovies = listedMovies.Where(ah => importExclusions.Any(h => ah.TmdbId.ToString() != h)).ToList();
}
//var downloadedCount = 0; //var downloadedCount = 0;
foreach (var movie in listedMovies) foreach (var movie in listedMovies)
{ {
var mapped = _movieSearch.MapMovieToTmdbMovie(movie); var mapped = _movieSearch.MapMovieToTmdbMovie(movie);
if (mapped != null && !importExclusions.Any(x => x == mapped.TmdbId.ToString())) if (mapped != null && !_exclusionService.IsMovieExcluded(mapped.TmdbId))
{ {
//List<DownloadDecision> decisions; //List<DownloadDecision> decisions;
mapped.AddOptions = new AddMovieOptions {SearchForMovie = true}; mapped.AddOptions = new AddMovieOptions {SearchForMovie = true};
@@ -22,7 +22,7 @@ namespace NzbDrone.Core.Notifications.Email
public EmailSettings() public EmailSettings()
{ {
Server = "smtp.google.com"; Server = "smtp.gmail.com";
Port = 587; Port = 587;
Ssl = true; Ssl = true;
} }
@@ -41,7 +41,7 @@ namespace NzbDrone.Core.Notifications.Xbmc
var parameters = new Dictionary<string, object>(); var parameters = new Dictionary<string, object>();
parameters.Add("title", title); parameters.Add("title", title);
parameters.Add("message", message); parameters.Add("message", message);
parameters.Add("image", "https://raw.github.com/Sonarr/Sonarr/develop/Logo/64.png"); parameters.Add("image", "https://raw.github.com/Radarr/Radarr/develop/Logo/64.png");
parameters.Add("displaytime", settings.DisplayTime * 1000); parameters.Add("displaytime", settings.DisplayTime * 1000);
ProcessRequest(request, settings, "GUI.ShowNotification", parameters); ProcessRequest(request, settings, "GUI.ShowNotification", parameters);
+12
View File
@@ -125,6 +125,7 @@
<Compile Include="Authentication\UserRepository.cs" /> <Compile Include="Authentication\UserRepository.cs" />
<Compile Include="Authentication\UserService.cs" /> <Compile Include="Authentication\UserService.cs" />
<Compile Include="Datastore\Migration\123_create_netimport_table.cs" /> <Compile Include="Datastore\Migration\123_create_netimport_table.cs" />
<Compile Include="MediaFiles\Events\MovieFileUpdatedEvent.cs" />
<Compile Include="Datastore\Migration\134_add_remux_qualities_for_the_wankers.cs" /> <Compile Include="Datastore\Migration\134_add_remux_qualities_for_the_wankers.cs" />
<Compile Include="Datastore\Migration\129_add_parsed_movie_info_to_pending_release.cs" /> <Compile Include="Datastore\Migration\129_add_parsed_movie_info_to_pending_release.cs" />
<Compile Include="Datastore\Migration\128_remove_kickass.cs" /> <Compile Include="Datastore\Migration\128_remove_kickass.cs" />
@@ -1275,6 +1276,14 @@
<Compile Include="Datastore\Migration\131_make_parsed_episode_info_nullable.cs" /> <Compile Include="Datastore\Migration\131_make_parsed_episode_info_nullable.cs" />
<Compile Include="Housekeeping\Housekeepers\FixWronglyMatchedMovieFiles.cs" /> <Compile Include="Housekeeping\Housekeepers\FixWronglyMatchedMovieFiles.cs" />
<Compile Include="Datastore\Migration\135_add_haspredbentry_to_movies.cs" /> <Compile Include="Datastore\Migration\135_add_haspredbentry_to_movies.cs" />
<Compile Include="MediaFiles\Commands\RenameMovieFolderCommand.cs" />
<Compile Include="Tv\QueryExtensions.cs" />
<Compile Include="Datastore\Migration\136_add_pathstate_to_movies.cs" />
<Compile Include="MetadataSource\IDiscoverNewMovies.cs" />
<Compile Include="Datastore\Migration\137_add_import_exclusions_table.cs" />
<Compile Include="NetImport\ImportExclusions\ImportExclusion.cs" />
<Compile Include="NetImport\ImportExclusions\ImportExclusionsRepository.cs" />
<Compile Include="NetImport\ImportExclusions\ImportExclusionsService.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.0,Profile=Client"> <BootstrapperPackage Include=".NETFramework,Version=v4.0,Profile=Client">
@@ -1344,6 +1353,9 @@
<Compile Include="Notifications\Telegram\TelegramError.cs" /> <Compile Include="Notifications\Telegram\TelegramError.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup />
<ItemGroup>
<Folder Include="NetImport\ImportExclusions\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup> <PropertyGroup>
<PostBuildEvent> <PostBuildEvent>
+69 -3
View File
@@ -20,6 +20,7 @@ namespace NzbDrone.Core.Organizer
string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null); string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null);
string BuildFilePath(Movie movie, string fileName, string extension); string BuildFilePath(Movie movie, string fileName, string extension);
string BuildFilePath(Series series, int seasonNumber, string fileName, string extension); string BuildFilePath(Series series, int seasonNumber, string fileName, string extension);
string BuildMoviePath(Movie movie, NamingConfig namingConfig = null);
string BuildSeasonPath(Series series, int seasonNumber); string BuildSeasonPath(Series series, int seasonNumber);
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec); BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
string GetSeriesFolder(Series series, NamingConfig namingConfig = null); string GetSeriesFolder(Series series, NamingConfig namingConfig = null);
@@ -158,12 +159,11 @@ namespace NzbDrone.Core.Organizer
return GetOriginalTitle(movieFile); return GetOriginalTitle(movieFile);
} }
//TODO: Update namingConfig for Movies!
var pattern = namingConfig.StandardMovieFormat; var pattern = namingConfig.StandardMovieFormat;
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance); var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
AddMovieTokens(tokenHandlers, movie); AddMovieTokens(tokenHandlers, movie);
AddReleaseDateTokens(tokenHandlers, movie.Year); //In case we want to separate the year AddReleaseDateTokens(tokenHandlers, movie.Year);
AddImdbIdTokens(tokenHandlers, movie.ImdbId); AddImdbIdTokens(tokenHandlers, movie.ImdbId);
AddQualityTokens(tokenHandlers, movie, movieFile); AddQualityTokens(tokenHandlers, movie, movieFile);
AddMediaInfoTokens(tokenHandlers, movieFile); AddMediaInfoTokens(tokenHandlers, movieFile);
@@ -190,11 +190,61 @@ namespace NzbDrone.Core.Organizer
{ {
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace(); Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
var path = movie.Path; var path = "";
if (movie.PathState > 0)
{
path = movie.Path;
}
else
{
path = BuildMoviePath(movie);
}
return Path.Combine(path, fileName + extension); return Path.Combine(path, fileName + extension);
} }
public string BuildMoviePath(Movie movie, NamingConfig namingConfig = null)
{
if (namingConfig == null)
{
namingConfig = _namingConfigService.GetConfig();
}
var path = movie.Path;
var directory = new DirectoryInfo(path).Name;
var parentDirectoryPath = new DirectoryInfo(path).Parent.FullName;
var movieFile = movie.MovieFile;
var pattern = namingConfig.MovieFolderFormat;
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
AddMovieTokens(tokenHandlers, movie);
AddReleaseDateTokens(tokenHandlers, movie.Year);
AddImdbIdTokens(tokenHandlers, movie.ImdbId);
if(movie.MovieFileId != 0)
{
movieFile.LazyLoad();
AddQualityTokens(tokenHandlers, movie, movieFile);
AddMediaInfoTokens(tokenHandlers, movieFile);
AddMovieFileTokens(tokenHandlers, movieFile);
AddTagsTokens(tokenHandlers, movieFile);
}
else
{
AddMovieFileTokens(tokenHandlers, new MovieFile { SceneName = $"{movie.Title} {movie.Year}", RelativePath = $"{movie.Title} {movie.Year}" });
}
var directoryName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
directoryName = FileNameCleanupRegex.Replace(directoryName, match => match.Captures[0].Value[0].ToString());
directoryName = TrimSeparatorsRegex.Replace(directoryName, string.Empty);
return Path.Combine(parentDirectoryPath, directoryName);
}
public string BuildSeasonPath(Series series, int seasonNumber) public string BuildSeasonPath(Series series, int seasonNumber)
{ {
var path = series.Path; var path = series.Path;
@@ -302,12 +352,28 @@ namespace NzbDrone.Core.Organizer
namingConfig = _namingConfigService.GetConfig(); namingConfig = _namingConfigService.GetConfig();
} }
var movieFile = movie.MovieFile;
var pattern = namingConfig.MovieFolderFormat;
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance); var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
AddMovieTokens(tokenHandlers, movie); AddMovieTokens(tokenHandlers, movie);
AddReleaseDateTokens(tokenHandlers, movie.Year); AddReleaseDateTokens(tokenHandlers, movie.Year);
AddImdbIdTokens(tokenHandlers, movie.ImdbId); AddImdbIdTokens(tokenHandlers, movie.ImdbId);
if (movie.MovieFileId != 0)
{
movieFile.LazyLoad();
AddQualityTokens(tokenHandlers, movie, movieFile);
AddMediaInfoTokens(tokenHandlers, movieFile);
AddMovieFileTokens(tokenHandlers, movieFile);
AddTagsTokens(tokenHandlers, movieFile);
}
else
{
AddMovieFileTokens(tokenHandlers, new MovieFile { SceneName = $"{movie.Title} {movie.Year}", RelativePath = $"{movie.Title} {movie.Year}"});
}
return CleanFolderName(ReplaceTokens(namingConfig.MovieFolderFormat, tokenHandlers, namingConfig)); return CleanFolderName(ReplaceTokens(namingConfig.MovieFolderFormat, tokenHandlers, namingConfig));
} }
@@ -44,13 +44,6 @@ namespace NzbDrone.Core.Organizer
{ {
_buildFileNames = buildFileNames; _buildFileNames = buildFileNames;
_movie = new Movie
{
Title = "The Movie Title",
Year = 2010,
ImdbId = "tt0066921"
};
_standardSeries = new Series _standardSeries = new Series
{ {
SeriesType = SeriesTypes.Standard, SeriesType = SeriesTypes.Standard,
@@ -122,13 +115,22 @@ namespace NzbDrone.Core.Organizer
_movieFile = new MovieFile _movieFile = new MovieFile
{ {
Quality = new QualityModel(Quality.Bluray1080p, new Revision(2)), Quality = new QualityModel(Quality.Bluray1080p, new Revision(2)),
RelativePath = "Movie.Title.2010.1080p.BluRay.DTS.x264-EVOLVE.mkv", RelativePath = "The.Movie.Title.2010.1080p.BluRay.DTS.x264-EVOLVE.mkv",
SceneName = "Movie.Title.2010.1080p.BluRay.DTS.x264-EVOLVE", SceneName = "The.Movie.Title.2010.1080p.BluRay.DTS.x264-EVOLVE",
ReleaseGroup = "RlsGrp", ReleaseGroup = "EVOLVE",
MediaInfo = mediaInfo, MediaInfo = mediaInfo,
Edition = "Ultimate extended edition", Edition = "Ultimate extended edition",
}; };
_movie = new Movie
{
Title = "The Movie: Title",
Year = 2010,
ImdbId = "tt0066921",
MovieFile = _movieFile,
MovieFileId = 1,
};
_singleEpisodeFile = new EpisodeFile _singleEpisodeFile = new EpisodeFile
{ {
Quality = new QualityModel(Quality.HDTV720p, new Revision(2)), Quality = new QualityModel(Quality.HDTV720p, new Revision(2)),
+14 -1
View File
@@ -1,4 +1,4 @@
using System; using System;
using System.Text; using System.Text;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
@@ -26,6 +26,8 @@ namespace NzbDrone.Core.Parser.Model
public string Codec { get; set; } public string Codec { get; set; }
public string Resolution { get; set; } public string Resolution { get; set; }
public IndexerFlags IndexerFlags { get; set; }
public int Age public int Age
{ {
get get
@@ -91,4 +93,15 @@ namespace NzbDrone.Core.Parser.Model
} }
} }
} }
[Flags]
public enum IndexerFlags
{
G_Freeleech = 1, //General
G_Halfleech = 2, //General, only 1/2 of download counted
G_DoubleUpload = 4, //General
PTP_Golden = 8, //PTP
PTP_Approved = 16, //PTP
HDB_Internal = 32 //HDBits
}
} }
+16 -5
View File
@@ -19,11 +19,11 @@ namespace NzbDrone.Core.Parser
private static readonly Regex[] ReportMovieTitleRegex = new[] private static readonly Regex[] ReportMovieTitleRegex = new[]
{ {
//Special, Despecialized, etc. Edition Movies, e.g: Mission.Impossible.3.Special.Edition.2011 //Special, Despecialized, etc. Edition Movies, e.g: Mission.Impossible.3.Special.Edition.2011
new Regex(@"^(?<title>(?![(\[]).+?)?(?:(?:[-_\W](?<![)\[!]))*(?<edition>(\.?((Extended.|Ultimate.)?(Director.?s|Collector.?s|Theatrical|Ultimate|Final|Extended|Rogue|Special|Despecialized).(Cut|Edition|Version)|Extended|Uncensored|Remastered|Unrated|Uncut|IMAX)))\.(?<year>(19|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)", new Regex(@"^(?<title>(?![(\[]).+?)?(?:(?:[-_\W](?<![)\[!]))*\(?(?<edition>(((Extended.|Ultimate.)?(Director.?s|Collector.?s|Theatrical|Ultimate|Final(?=(.(Cut|Edition|Version)))|Extended|Rogue|Special|Despecialized|\d{2,3}(th)?.Anniversary)(.(Cut|Edition|Version))?(.(Extended|Uncensored|Remastered|Unrated|Uncut|IMAX|Fan.?Edit))?|((Uncensored|Remastered|Unrated|Uncut|IMAX|Fan.?Edit|Edition|Restored|((2|3|4)in1))))))\)?.+(?<year>(19|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)",
RegexOptions.IgnoreCase | RegexOptions.Compiled), RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Special, Despecialized, etc. Edition Movies, e.g: Mission.Impossible.3.2011.Special.Edition //TODO: Seems to slow down parsing heavily! //Special, Despecialized, etc. Edition Movies, e.g: Mission.Impossible.3.2011.Special.Edition //TODO: Seems to slow down parsing heavily!
new Regex(@"^(?<title>(?![(\[]).+?)?(?:(?:[-_\W](?<![)\[!]))*(?<year>(19|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)(?<edition>((Extended.|Ultimate.)?(Director.?s|Collector.?s|Theatrical|Ultimate|Final|Extended|Rogue|Special|Despecialized).(Cut|Edition|Version)|Extended|Uncensored|Remastered|Unrated|Uncut|IMAX))", new Regex(@"^(?<title>(?![(\[]).+?)?(?:(?:[-_\W](?<![)\[!]))*(?<year>(19|20)\d{2}(?!p|i|(19|20)\d{2}|\]|\W(19|20)\d{2})))+(\W+|_|$)(?!\\)\(?(?<edition>(((Extended.|Ultimate.)?(Director.?s|Collector.?s|Theatrical|Ultimate|Final(?=(.(Cut|Edition|Version)))|Extended|Rogue|Special|Despecialized|\d{2,3}(th)?.Anniversary)(.(Cut|Edition|Version))?(.(Extended|Uncensored|Remastered|Unrated|Uncut|IMAX|Fan.?Edit))?|((Uncensored|Remastered|Unrated|Uncut|IMAX|Fan.?Edit|Edition|Restored|((2|3|4)in1))))))\)?",
RegexOptions.IgnoreCase | RegexOptions.Compiled), RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Normal movie format, e.g: Mission.Impossible.3.2011 //Normal movie format, e.g: Mission.Impossible.3.2011
@@ -273,7 +273,7 @@ namespace NzbDrone.Core.Parser
private static readonly Regex ReportImdbId = new Regex(@"(?<imdbid>tt\d{7})", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex ReportImdbId = new Regex(@"(?<imdbid>tt\d{7})", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex SimpleTitleRegex = new Regex(@"(?:480[ip]|576[ip]|720[ip]|1080[ip]|2160[ip]|[xh][\W_]?26[45]|DD\W?5\W1|[<>?*:|]|848x480|1280x720|1920x1080|(8|10)b(it)?)\s*", private static readonly Regex SimpleTitleRegex = new Regex(@"\s*(?:480[ip]|576[ip]|720[ip]|1080[ip]|2160[ip]|[xh][\W_]?26[45]|DD\W?5\W1|[<>?*:|]|848x480|1280x720|1920x1080|(8|10)b(it)?)",
RegexOptions.IgnoreCase | RegexOptions.Compiled); RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex WebsitePrefixRegex = new Regex(@"^\[\s*[a-z]+(\.[a-z]+)+\s*\][- ]*", private static readonly Regex WebsitePrefixRegex = new Regex(@"^\[\s*[a-z]+(\.[a-z]+)+\s*\][- ]*",
@@ -310,6 +310,12 @@ namespace NzbDrone.Core.Parser
private static readonly Regex RequestInfoRegex = new Regex(@"\[.+?\]", RegexOptions.Compiled); private static readonly Regex RequestInfoRegex = new Regex(@"\[.+?\]", RegexOptions.Compiled);
private static readonly string[] Numbers = new[] { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" }; private static readonly string[] Numbers = new[] { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };
private static Dictionary<String, String> _umlautMappings = new Dictionary<string, string>
{
{"ö", "oe"},
{"ä", "ae"},
{"ü", "ue"},
};
public static ParsedEpisodeInfo ParsePath(string path) public static ParsedEpisodeInfo ParsePath(string path)
{ {
@@ -405,8 +411,13 @@ namespace NzbDrone.Core.Parser
if (result != null) if (result != null)
{ {
var languageTitle = simpleTitle;
if (match[0].Groups["title"].Success && match[0].Groups["title"].Value.IsNotNullOrWhiteSpace())
{
languageTitle = simpleTitle.Replace(match[0].Groups["title"].Value, "A Movie");
}
result.Language = LanguageParser.ParseLanguage(simpleTitle.Replace(result.MovieTitle, "A Movie")); result.Language = LanguageParser.ParseLanguage(languageTitle);
Logger.Debug("Language parsed: {0}", result.Language); Logger.Debug("Language parsed: {0}", result.Language);
result.Quality = QualityParser.ParseQuality(title); result.Quality = QualityParser.ParseQuality(title);
@@ -655,7 +666,7 @@ namespace NzbDrone.Core.Parser
if (long.TryParse(title, out number)) if (long.TryParse(title, out number))
return title; return title;
return NormalizeRegex.Replace(title, string.Empty).ToLower().RemoveAccent(); return ReplaceGermanUmlauts(NormalizeRegex.Replace(title, string.Empty).ToLower()).RemoveAccent();
} }
public static string NormalizeEpisodeTitle(string title) public static string NormalizeEpisodeTitle(string title)
+37 -4
View File
@@ -36,6 +36,7 @@ namespace NzbDrone.Core.Parser
private readonly IMovieService _movieService; private readonly IMovieService _movieService;
private readonly Logger _logger; private readonly Logger _logger;
private static HashSet<ArabicRomanNumeral> _arabicRomanNumeralMappings; private static HashSet<ArabicRomanNumeral> _arabicRomanNumeralMappings;
public ParsingService(IEpisodeService episodeService, public ParsingService(IEpisodeService episodeService,
ISeriesService seriesService, ISeriesService seriesService,
@@ -178,7 +179,7 @@ namespace NzbDrone.Core.Parser
return _movieService.FindByTitle(title); return _movieService.FindByTitle(title);
} }
var movies = _movieService.FindByTitle(parsedMovieInfo.MovieTitle); var movies = _movieService.FindByTitle(parsedMovieInfo.MovieTitle, parsedMovieInfo.Year);
if (movies == null) if (movies == null)
{ {
@@ -349,7 +350,7 @@ namespace NzbDrone.Core.Parser
private Movie GetMovie(ParsedMovieInfo parsedMovieInfo, string imdbId, SearchCriteriaBase searchCriteria) private Movie GetMovie(ParsedMovieInfo parsedMovieInfo, string imdbId, SearchCriteriaBase searchCriteria)
{ {
// TODO: Answer me this: Wouldn't it be smarter to start out looking for a movie if we have an ImDb Id? // 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)) if (!String.IsNullOrWhiteSpace(imdbId) && imdbId != "0")
{ {
Movie movieByImDb; Movie movieByImDb;
if (TryGetMovieByImDbId(parsedMovieInfo, imdbId, out movieByImDb)) if (TryGetMovieByImDbId(parsedMovieInfo, imdbId, out movieByImDb))
@@ -402,6 +403,8 @@ namespace NzbDrone.Core.Parser
{ {
return true; return true;
} }
return false;
} }
movieByTitleAndOrYear = _movieService.FindByTitle(parsedMovieInfo.MovieTitle); movieByTitleAndOrYear = _movieService.FindByTitle(parsedMovieInfo.MovieTitle);
if (isNotNull(movieByTitleAndOrYear)) if (isNotNull(movieByTitleAndOrYear))
@@ -412,7 +415,7 @@ namespace NzbDrone.Core.Parser
return false; return false;
} }
private static bool TryGetMovieBySearchCriteria(ParsedMovieInfo parsedMovieInfo, SearchCriteriaBase searchCriteria, out Movie possibleMovie) private bool TryGetMovieBySearchCriteria(ParsedMovieInfo parsedMovieInfo, SearchCriteriaBase searchCriteria, out Movie possibleMovie)
{ {
possibleMovie = null; possibleMovie = null;
List<string> possibleTitles = new List<string>(); List<string> possibleTitles = new List<string>();
@@ -424,6 +427,8 @@ namespace NzbDrone.Core.Parser
possibleTitles.Add(altTitle.CleanSeriesTitle()); possibleTitles.Add(altTitle.CleanSeriesTitle());
} }
string cleanTitle = parsedMovieInfo.MovieTitle.CleanSeriesTitle();
foreach (string title in possibleTitles) foreach (string title in possibleTitles)
{ {
if (title == parsedMovieInfo.MovieTitle.CleanSeriesTitle()) if (title == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
@@ -436,12 +441,14 @@ namespace NzbDrone.Core.Parser
string arabicNumeral = numeralMapping.ArabicNumeralAsString; string arabicNumeral = numeralMapping.ArabicNumeralAsString;
string romanNumeral = numeralMapping.RomanNumeralLowerCase; string romanNumeral = numeralMapping.RomanNumeralLowerCase;
_logger.Debug(cleanTitle);
if (title.Replace(arabicNumeral, romanNumeral) == parsedMovieInfo.MovieTitle.CleanSeriesTitle()) if (title.Replace(arabicNumeral, romanNumeral) == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
{ {
possibleMovie = searchCriteria.Movie; possibleMovie = searchCriteria.Movie;
} }
if (title.Replace(romanNumeral, arabicNumeral) == parsedMovieInfo.MovieTitle.CleanSeriesTitle()) if (title == parsedMovieInfo.MovieTitle.CleanSeriesTitle().Replace(arabicNumeral, romanNumeral))
{ {
possibleMovie = searchCriteria.Movie; possibleMovie = searchCriteria.Movie;
} }
@@ -681,4 +688,30 @@ namespace NzbDrone.Core.Parser
return result; return result;
} }
} }
public class MappingException : Exception
{
public virtual string Reason()
{
return "Parsed movie does not match wanted movie";
}
}
public class YearDoesNotMatchException : MappingException
{
public int ExpectedYear { get; set; }
public int? ParsedYear { get; set; }
override public string Reason()
{
if (ParsedYear.HasValue && ParsedYear > 1800)
{
return $"Expected {ExpectedYear}, but found {ParsedYear} for year";
}
else
{
return "Did not find a valid year";
}
}
}
} }
+16 -2
View File
@@ -41,6 +41,7 @@ namespace NzbDrone.Core.Tv
public List<Actor> Actors { get; set; } public List<Actor> Actors { get; set; }
public string Certification { get; set; } public string Certification { get; set; }
public string RootFolderPath { get; set; } public string RootFolderPath { get; set; }
public MoviePathState PathState { get; set; }
public DateTime Added { get; set; } public DateTime Added { get; set; }
public DateTime? InCinemas { get; set; } public DateTime? InCinemas { get; set; }
public DateTime? PhysicalRelease { get; set; } public DateTime? PhysicalRelease { get; set; }
@@ -98,12 +99,18 @@ namespace NzbDrone.Core.Tv
return true; return true;
} }
return DateTime.Now >= MinimumAvailabilityDate.AddDays((double)delay); if (MinimumAvailabilityDate == DateTime.MinValue || MinimumAvailabilityDate == DateTime.MaxValue)
{
return DateTime.Now >= MinimumAvailabilityDate;
}
return DateTime.Now >= MinimumAvailabilityDate.AddDays((double)delay);
} }
public override string ToString() public override string ToString()
{ {
return string.Format("[{0}][{1}]", ImdbId, Title.NullSafe()); return string.Format("[{0}][{1} ({2})]", ImdbId, Title.NullSafe(), Year.NullSafe());
} }
} }
@@ -111,4 +118,11 @@ namespace NzbDrone.Core.Tv
{ {
public bool SearchForMovie { get; set; } public bool SearchForMovie { get; set; }
} }
public enum MoviePathState
{
Dynamic,
StaticOnce,
Static,
}
} }
+12 -10
View File
@@ -238,29 +238,31 @@ namespace NzbDrone.Core.Tv
cleanTitleWithArabicNumbers = cleanTitleWithArabicNumbers.Replace(romanNumber, arabicNumber); cleanTitleWithArabicNumbers = cleanTitleWithArabicNumbers.Replace(romanNumber, arabicNumber);
} }
IEnumerable<Movie> results = Query.Where(s => s.CleanTitle == cleanTitle); Movie result = Query.Where(s => s.CleanTitle == cleanTitle).FirstWithYear(year);
if (results == null) if (result == null)
{ {
results = Query.Where(movie => movie.CleanTitle == cleanTitleWithArabicNumbers) ?? result = Query.Where(movie => movie.CleanTitle == cleanTitleWithArabicNumbers).FirstWithYear(year) ??
Query.Where(movie => movie.CleanTitle == cleanTitleWithRomanNumbers); Query.Where(movie => movie.CleanTitle == cleanTitleWithRomanNumbers).FirstWithYear(year);
if (results == null) if (result == null)
{ {
IEnumerable<Movie> movies = All(); IEnumerable<Movie> movies = All();
Func<string, string> titleCleaner = title => CoreParser.CleanSeriesTitle(title.ToLower()); Func<string, string> titleCleaner = title => CoreParser.CleanSeriesTitle(title.ToLower());
Func<IEnumerable<string>, string, bool> altTitleComparer = Func<IEnumerable<string>, string, bool> altTitleComparer =
(alternativeTitles, atitle) => (alternativeTitles, atitle) =>
alternativeTitles.Any(altTitle => altTitle == titleCleaner(atitle)); alternativeTitles.Any(altTitle => titleCleaner(altTitle) == atitle);
results = movies.Where(m => altTitleComparer(m.AlternativeTitles, cleanTitle) || result = movies.Where(m => altTitleComparer(m.AlternativeTitles, cleanTitle) ||
altTitleComparer(m.AlternativeTitles, cleanTitleWithRomanNumbers) || altTitleComparer(m.AlternativeTitles, cleanTitleWithRomanNumbers) ||
altTitleComparer(m.AlternativeTitles, cleanTitleWithArabicNumbers)); altTitleComparer(m.AlternativeTitles, cleanTitleWithArabicNumbers)).FirstWithYear(year);
} }
} }
return year.HasValue return result;
/*return year.HasValue
? results?.FirstOrDefault(movie => movie.Year == year.Value) ? results?.FirstOrDefault(movie => movie.Year == year.Value)
: results?.FirstOrDefault(); : results?.FirstOrDefault();*/
} }
public Movie FindByTmdbId(int tmdbid) public Movie FindByTmdbId(int tmdbid)
+27 -9
View File
@@ -14,6 +14,7 @@ using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.NetImport.ImportExclusions;
namespace NzbDrone.Core.Tv namespace NzbDrone.Core.Tv
{ {
@@ -51,20 +52,24 @@ namespace NzbDrone.Core.Tv
private readonly IConfigService _configService; private readonly IConfigService _configService;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly IBuildFileNames _fileNameBuilder; private readonly IBuildFileNames _fileNameBuilder;
private readonly IImportExclusionsService _exclusionService;
private readonly Logger _logger; private readonly Logger _logger;
public MovieService(IMovieRepository movieRepository, public MovieService(IMovieRepository movieRepository,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
ISceneMappingService sceneMappingService, ISceneMappingService sceneMappingService,
IEpisodeService episodeService, IEpisodeService episodeService,
IBuildFileNames fileNameBuilder, IBuildFileNames fileNameBuilder,
IConfigService configService, IConfigService configService,
IImportExclusionsService exclusionService,
Logger logger) Logger logger)
{ {
_movieRepository = movieRepository; _movieRepository = movieRepository;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_fileNameBuilder = fileNameBuilder; _fileNameBuilder = fileNameBuilder;
_configService = configService; _configService = configService;
_exclusionService = exclusionService;
_logger = logger; _logger = logger;
} }
@@ -149,13 +154,23 @@ namespace NzbDrone.Core.Tv
{ {
Ensure.That(newMovie, () => newMovie).IsNotNull(); Ensure.That(newMovie, () => newMovie).IsNotNull();
MoviePathState defaultState = MoviePathState.Static;
if (!_configService.PathsDefaultStatic)
{
defaultState = MoviePathState.Dynamic;
}
if (string.IsNullOrWhiteSpace(newMovie.Path)) if (string.IsNullOrWhiteSpace(newMovie.Path))
{ {
var folderName = _fileNameBuilder.GetMovieFolder(newMovie); var folderName = _fileNameBuilder.GetMovieFolder(newMovie);
newMovie.Path = Path.Combine(newMovie.RootFolderPath, folderName); newMovie.Path = Path.Combine(newMovie.RootFolderPath, folderName);
newMovie.PathState = defaultState;
}
else
{
newMovie.PathState = defaultState == MoviePathState.Dynamic ? MoviePathState.StaticOnce : MoviePathState.Static;
} }
_logger.Info("Adding Movie {0} Path: [{1}]", newMovie, newMovie.Path); _logger.Info("Adding Movie {0} Path: [{1}]", newMovie, newMovie.Path);
newMovie.CleanTitle = newMovie.Title.CleanSeriesTitle(); newMovie.CleanTitle = newMovie.Title.CleanSeriesTitle();
newMovie.SortTitle = MovieTitleNormalizer.Normalize(newMovie.Title, newMovie.TmdbId); newMovie.SortTitle = MovieTitleNormalizer.Normalize(newMovie.Title, newMovie.TmdbId);
@@ -173,10 +188,20 @@ namespace NzbDrone.Core.Tv
newMovies.ForEach(m => newMovies.ForEach(m =>
{ {
MoviePathState defaultState = MoviePathState.Static;
if (!_configService.PathsDefaultStatic)
{
defaultState = MoviePathState.Dynamic;
}
if (string.IsNullOrWhiteSpace(m.Path)) if (string.IsNullOrWhiteSpace(m.Path))
{ {
var folderName = _fileNameBuilder.GetMovieFolder(m); var folderName = _fileNameBuilder.GetMovieFolder(m);
m.Path = Path.Combine(m.RootFolderPath, folderName); m.Path = Path.Combine(m.RootFolderPath, folderName);
m.PathState = defaultState;
}
else
{
m.PathState = defaultState == MoviePathState.Dynamic ? MoviePathState.StaticOnce : MoviePathState.Static;
} }
m.CleanTitle = m.Title.CleanSeriesTitle(); m.CleanTitle = m.Title.CleanSeriesTitle();
@@ -265,14 +290,7 @@ namespace NzbDrone.Core.Tv
var movie = _movieRepository.Get(movieId); var movie = _movieRepository.Get(movieId);
if (addExclusion) if (addExclusion)
{ {
if (_configService.ImportExclusions.Empty()) _exclusionService.AddExclusion(new ImportExclusion {TmdbId = movie.TmdbId, MovieTitle = movie.Title, MovieYear = movie.Year } );
{
_configService.ImportExclusions = movie.TitleSlug;
}
else if (!_configService.ImportExclusions.Contains(movie.TitleSlug))
{
_configService.ImportExclusions += ',' + movie.TitleSlug;
}
} }
_movieRepository.Delete(movieId); _movieRepository.Delete(movieId);
_eventAggregator.PublishEvent(new MovieDeletedEvent(movie, deleteFiles)); _eventAggregator.PublishEvent(new MovieDeletedEvent(movie, deleteFiles));
+30
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();
}
}
}
@@ -13,6 +13,7 @@ using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.MetadataSource; using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Tv.Commands; using NzbDrone.Core.Tv.Commands;
using NzbDrone.Core.Tv.Events; using NzbDrone.Core.Tv.Events;
using NzbDrone.Core.MediaFiles.Commands;
namespace NzbDrone.Core.Tv namespace NzbDrone.Core.Tv
{ {
@@ -22,6 +23,7 @@ namespace NzbDrone.Core.Tv
private readonly IMovieService _movieService; private readonly IMovieService _movieService;
private readonly IRefreshEpisodeService _refreshEpisodeService; private readonly IRefreshEpisodeService _refreshEpisodeService;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly IManageCommandQueue _commandQueueManager;
private readonly IDiskScanService _diskScanService; private readonly IDiskScanService _diskScanService;
private readonly ICheckIfMovieShouldBeRefreshed _checkIfMovieShouldBeRefreshed; private readonly ICheckIfMovieShouldBeRefreshed _checkIfMovieShouldBeRefreshed;
private readonly Logger _logger; private readonly Logger _logger;
@@ -32,12 +34,14 @@ namespace NzbDrone.Core.Tv
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
IDiskScanService diskScanService, IDiskScanService diskScanService,
ICheckIfMovieShouldBeRefreshed checkIfMovieShouldBeRefreshed, ICheckIfMovieShouldBeRefreshed checkIfMovieShouldBeRefreshed,
IManageCommandQueue commandQueue,
Logger logger) Logger logger)
{ {
_movieInfo = movieInfo; _movieInfo = movieInfo;
_movieService = movieService; _movieService = movieService;
_refreshEpisodeService = refreshEpisodeService; _refreshEpisodeService = refreshEpisodeService;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_commandQueueManager = commandQueue;
_diskScanService = diskScanService; _diskScanService = diskScanService;
_checkIfMovieShouldBeRefreshed = checkIfMovieShouldBeRefreshed; _checkIfMovieShouldBeRefreshed = checkIfMovieShouldBeRefreshed;
_logger = logger; _logger = logger;
@@ -136,6 +140,7 @@ namespace NzbDrone.Core.Tv
try try
{ {
_logger.Info("Skipping refresh of movie: {0}", movie.Title); _logger.Info("Skipping refresh of movie: {0}", movie.Title);
_commandQueueManager.Push(new RenameMovieFolderCommand(new List<int>{movie.Id}));
_diskScanService.Scan(movie); _diskScanService.Scan(movie);
} }
catch (Exception e) catch (Exception e)
+93 -3
View File
@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using Mono.Unix; using Mono.Unix;
using Mono.Unix.Native; using Mono.Unix.Native;
using NLog; using NLog;
@@ -17,21 +18,50 @@ namespace NzbDrone.Mono.Disk
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(DiskProvider)); private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(DiskProvider));
private readonly IProcMountProvider _procMountProvider; private readonly IProcMountProvider _procMountProvider;
private readonly NzbDrone.Mono.Disk.ISymbolicLinkResolver _symLinkResolver; private readonly ISymbLinkResolver _symLinkResolver;
private readonly Logger _logger;
// Mono supports sending -1 for a uint to indicate that the owner or group should not be set // Mono supports sending -1 for a uint to indicate that the owner or group should not be set
// `unchecked((uint)-1)` and `uint.MaxValue` are the same thing. // `unchecked((uint)-1)` and `uint.MaxValue` are the same thing.
private const uint UNCHANGED_ID = uint.MaxValue; private const uint UNCHANGED_ID = uint.MaxValue;
public DiskProvider(IProcMountProvider procMountProvider, NzbDrone.Mono.Disk.ISymbolicLinkResolver symLinkResolver) public DiskProvider(IProcMountProvider procMountProvider, ISymbLinkResolver symLinkResolver, Logger logger)
{ {
_procMountProvider = procMountProvider; _procMountProvider = procMountProvider;
_symLinkResolver = symLinkResolver; _symLinkResolver = symLinkResolver;
_logger = logger;
} }
public override IMount GetMount(string path) public override IMount GetMount(string path)
{ {
path = _symLinkResolver.GetCompleteRealPath(path); if (path == null) return null;
try
{
string[] dirs;
int lastIndex;
GetPathComponents(path, out dirs, out lastIndex);
var realPath = new StringBuilder();
if (dirs.Length > 0)
{
var dir = UnixPath.IsPathRooted(path) ? "/" : "";
dir += dirs[0];
realPath.Append(GetRealPath(dir));
}
for (var i = 1; i < lastIndex; ++i)
{
realPath.Append("/").Append(dirs[i]);
var realSubPath = GetRealPath(realPath.ToString());
realPath.Remove(0, realPath.Length);
realPath.Append(realSubPath);
}
path = realPath.ToString();
}
catch (Exception ex)
{
_logger.Debug(ex, string.Format("Failed to check for symlinks in the path {0}", path));
}
return base.GetMount(path); return base.GetMount(path);
} }
@@ -219,5 +249,65 @@ namespace NzbDrone.Mono.Disk
} }
private static void GetPathComponents(string path, out string[] components, out int lastIndex)
{
var dirs = path.Split(UnixPath.DirectorySeparatorChar);
var target = 0;
for (var i = 0; i < dirs.Length; ++i)
{
if (dirs[i] == "." || dirs[i] == string.Empty)
{
continue;
}
if (dirs[i] == "..")
{
if (target != 0)
{
target--;
}
else
{
target++;
}
}
else
{
dirs[target++] = dirs[i];
}
}
components = dirs;
lastIndex = target;
}
public string GetRealPath(string path)
{
do
{
var link = UnixPath.TryReadLink(path);
if (link == null)
{
var errno = Stdlib.GetLastError();
if (errno != Errno.EINVAL)
{
_logger.Trace("Checking path {0} for symlink returned error {1}, assuming it's not a symlink.", path, errno);
}
return path;
}
if (UnixPath.IsPathRooted(link))
{
path = link;
}
else
{
path = UnixPath.GetDirectoryName(path) + UnixPath.DirectorySeparatorChar + link;
path = UnixPath.GetCanonicalPath(path);
}
} while (true);
}
} }
} }
@@ -6,23 +6,23 @@ using NLog;
namespace NzbDrone.Mono.Disk namespace NzbDrone.Mono.Disk
{ {
public interface ISymbolicLinkResolver public interface ISymbLinkResolver
{ {
string GetCompleteRealPath(string path); string GetCompletePath(string path);
} }
// Mono's own implementation doesn't handle exceptions very well. // Mono's own implementation doesn't handle exceptions very well.
// All of this code was copied from mono with minor changes. // All of this code was copied from mono with minor changes.
public class SymbolicLinkResolver : ISymbolicLinkResolver public class SymbLinkResolver : ISymbLinkResolver
{ {
private readonly Logger _logger; private readonly Logger _logger;
public SymbolicLinkResolver(Logger logger) public SymbLinkResolver(Logger logger)
{ {
_logger = logger; _logger = logger;
} }
public string GetCompleteRealPath(string path) public string GetCompletePath(string path)
{ {
if (path == null) return null; if (path == null) return null;
+7 -5
View File
@@ -8,6 +8,7 @@ var ProfileCollection = require('../Profile/ProfileCollection');
var AddFromListView = require("./List/AddFromListView"); var AddFromListView = require("./List/AddFromListView");
var RootFolderCollection = require('./RootFolders/RootFolderCollection'); var RootFolderCollection = require('./RootFolders/RootFolderCollection');
var BulkImportView = require("./BulkImport/BulkImportView"); var BulkImportView = require("./BulkImport/BulkImportView");
var DiscoverMoviesCollection = require("./DiscoverMoviesCollection");
require('../Movies/MoviesCollection'); require('../Movies/MoviesCollection');
module.exports = Marionette.Layout.extend({ module.exports = Marionette.Layout.extend({
@@ -22,7 +23,7 @@ module.exports = Marionette.Layout.extend({
}, },
events : { events : {
'click .x-import' : '_importMovies', 'click .x-discover' : '_discoverMovies',
'click .x-bulk-import' : '_bulkImport', 'click .x-bulk-import' : '_bulkImport',
'click .x-add-new' : '_addMovies', 'click .x-add-new' : '_addMovies',
"click .x-add-lists" : "_addFromList", "click .x-add-lists" : "_addFromList",
@@ -70,10 +71,11 @@ module.exports = Marionette.Layout.extend({
this.workspace.show(new BulkImportView({ model : options.model})); this.workspace.show(new BulkImportView({ model : options.model}));
}, },
_importMovies : function() { _discoverMovies : function(options) {
this.rootFolderLayout = new RootFolderLayout(); options = options || {};
this.listenTo(this.rootFolderLayout, 'folderSelected', this._folderSelected); options.action = "discover";
AppLayout.modalRegion.show(this.rootFolderLayout); options.collection = new DiscoverMoviesCollection();
this.workspace.show(new AddMoviesView(options));
}, },
_addMovies : function(options) { _addMovies : function(options) {
+5 -8
View File
@@ -1,13 +1,10 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="btn-group add-movies-btn-group btn-group-lg btn-block"> <div class="btn-group add-movies-btn-group btn-group-lg btn-block btn-group-collapse">
<button class="btn btn-default col-md-3 col-xs-4 x-bulk-import"><i class="icon-sonarr-view-list hidden-xs"></i> Bulk Import Movies</button> <button class="btn btn-default col-md-3 col-xs-12 x-bulk-import"><i class="icon-sonarr-view-list hidden-xs"></i> Bulk Import Movies</button>
<button type="button" class="btn btn-default col-md-4 col-xs-4 add-movies-import-btn x-import"> <button type="button" class="btn btn-default col-md-4 col-xs-12 add-movies-import-btn x-discover"><i class="icon-sonarr-star"/> Discover new movies</button>
<i class="icon-sonarr-hdd"/> <button class="btn btn-default col-md-2 col-xs-12 x-add-new"><i class="icon-sonarr-active hidden-xs"></i> Add New Movie</button>
Import existing movies on disk <button class="btn btn-default col-md-3 col-xs-12 x-add-lists"><i class="icon-sonarr-active hidden-xs"></i> Add Movies from Lists</button>
</button>
<button class="btn btn-default col-md-2 col-xs-4 x-add-new"><i class="icon-sonarr-active hidden-xs"></i> Add New Movie</button>
<button class="btn btn-default col-md-3 col-xs-4 x-add-lists"><i class="icon-sonarr-active hidden-xs"></i> Add Movies from Lists</button>
</div> </div>
</div> </div>
</div> </div>
+75 -7
View File
@@ -1,12 +1,15 @@
var _ = require('underscore'); var _ = require('underscore');
var $ = require('jquery');
var vent = require('vent'); var vent = require('vent');
var Marionette = require('marionette'); var Marionette = require('marionette');
var AddMoviesCollection = require('./AddMoviesCollection'); var AddMoviesCollection = require('./AddMoviesCollection');
var SearchResultCollectionView = require('./SearchResultCollectionView'); var SearchResultCollectionView = require('./SearchResultCollectionView');
var EmptyView = require('./EmptyView'); var EmptyView = require('./EmptyView');
var NotFoundView = require('./NotFoundView'); var NotFoundView = require('./NotFoundView');
var DiscoverEmptyView = require('./DiscoverEmptyView');
var ErrorView = require('./ErrorView'); var ErrorView = require('./ErrorView');
var LoadingView = require('../Shared/LoadingView'); var LoadingView = require('../Shared/LoadingView');
var FullMovieCollection = require("../Movies/FullMovieCollection");
module.exports = Marionette.Layout.extend({ module.exports = Marionette.Layout.extend({
template : 'AddMovies/AddMoviesViewTemplate', template : 'AddMovies/AddMoviesViewTemplate',
@@ -18,16 +21,24 @@ module.exports = Marionette.Layout.extend({
ui : { ui : {
moviesSearch : '.x-movies-search', moviesSearch : '.x-movies-search',
searchBar : '.x-search-bar', searchBar : '.x-search-bar',
loadMore : '.x-load-more' loadMore : '.x-load-more',
discoverHeader : ".x-discover-header",
discoverBefore : ".x-discover-before",
discoverRecos : ".x-recommendations-tab",
discoverPopular : ".x-popular-tab" ,
discoverUpcoming : ".x-upcoming-tab"
}, },
events : { events : {
'click .x-load-more' : '_onLoadMore' 'click .x-load-more' : '_onLoadMore',
"click .x-recommendations-tab" : "_discoverRecos",
"click .x-popular-tab" : "_discoverPopular",
"click .x-upcoming-tab" : "_discoverUpcoming"
}, },
initialize : function(options) { initialize : function(options) {
this.isExisting = options.isExisting; this.isExisting = options.isExisting;
this.collection = new AddMoviesCollection(); this.collection = options.collection || new AddMoviesCollection();
if (this.isExisting) { if (this.isExisting) {
this.collection.unmappedFolderModel = this.model; this.collection.unmappedFolderModel = this.model;
@@ -51,6 +62,8 @@ module.exports = Marionette.Layout.extend({
if (options.action === "search") { if (options.action === "search") {
this.search({term: options.query}); this.search({term: options.query});
} else if (options.action == "discover") {
this.isDiscover = true;
} }
}, },
@@ -58,6 +71,8 @@ module.exports = Marionette.Layout.extend({
onRender : function() { onRender : function() {
var self = this; var self = this;
this.$el.addClass(this.className); this.$el.addClass(this.className);
this.ui.moviesSearch.keyup(function(e) { this.ui.moviesSearch.keyup(function(e) {
@@ -95,10 +110,24 @@ module.exports = Marionette.Layout.extend({
if (this.isExisting) { if (this.isExisting) {
this.ui.searchBar.hide(); this.ui.searchBar.hide();
} }
if (this.isDiscover) {
this.ui.searchBar.hide();
this._discoverRecos();
/*if (this.collection.length == 0) {
this.searchResult.show(new LoadingView());
}*/
}
}, },
onShow : function() { onShow : function() {
this.ui.discoverBefore.hide();
this.ui.moviesSearch.focus(); this.ui.moviesSearch.focus();
this.ui.loadMore.hide();
if (this.isDiscover) {
this.ui.discoverBefore.show();
}
}, },
search : function(options) { search : function(options) {
@@ -140,7 +169,10 @@ module.exports = Marionette.Layout.extend({
_onLoadMore : function() { _onLoadMore : function() {
var showingAll = this.resultCollectionView.showMore(); var showingAll = this.resultCollectionView.showMore();
this.ui.searchBar.show(); if (!this.isDiscover) {
this.ui.searchBar.show();
}
if (showingAll) { if (showingAll) {
this.ui.loadMore.hide(); this.ui.loadMore.hide();
@@ -159,8 +191,14 @@ module.exports = Marionette.Layout.extend({
_showResults : function() { _showResults : function() {
if (!this.isClosed) { if (!this.isClosed) {
if (this.collection.length === 0) { if (this.collection.length === 0) {
this.ui.searchBar.show(); this.ui.loadMore.hide();
this.searchResult.show(new NotFoundView({ term : this.collection.term })); if (this.isDiscover) {
this.searchResult.show(new DiscoverEmptyView());
} else {
this.ui.searchBar.show();
this.searchResult.show(new NotFoundView({ term : this.collection.term }));
}
} else { } else {
this.searchResult.show(this.resultCollectionView); this.searchResult.show(this.resultCollectionView);
if (!this.showingAll) { if (!this.showingAll) {
@@ -185,5 +223,35 @@ module.exports = Marionette.Layout.extend({
this.searchResult.show(new ErrorView({ term : this.collection.term })); this.searchResult.show(new ErrorView({ term : this.collection.term }));
this.collection.term = ''; this.collection.term = '';
} }
} },
_discover : function(action) {
if (this.collection.action === action) {
return
}
this.collection.reset();
this.searchResult.show(new LoadingView());
this.collection.action = action;
this.currentSearchPromise = this.collection.fetch();
},
_discoverRecos : function() {
this.ui.discoverRecos.tab("show");
this.ui.discoverHeader.html("Recommendations by The Movie Database for you");
this._discover("recommendations");
},
_discoverPopular : function() {
this.ui.discoverPopular.tab("show");
this.ui.discoverHeader.html("Currently Popular Movies");
this._discover("popular");
},
_discoverUpcoming : function() {
this.ui.discoverUpcoming.tab("show");
this.ui.discoverHeader.html("Movies coming to Blu-Ray in the next weeks");
this._discover("upcoming");
},
}); });
@@ -4,6 +4,18 @@
{{folder.path}} {{folder.path}}
</div> </div>
</div>{{/if}} </div>{{/if}}
<div class="x-discover-before">
<ul class="nav nav-tabs nav-justified settings-tabs">
<li><a href="#media-management" class="x-recommendations-tab no-router">Recommendations</a></li>
<li><a href="#popular" class="x-popular-tab no-router">Popular</a></li>
<li><a href="#upcoming" class="x-upcoming-tab no-router">Upcoming</a></li>
</ul>
<h2 class="x-discover-header">
Recommendations by The Movie Database based on your library:
</h2>
</div>
<div class="x-search-bar"> <div class="x-search-bar">
<div class="input-group input-group-lg add-movies-search"> <div class="input-group input-group-lg add-movies-search">
<span class="input-group-addon"><i class="icon-sonarr-search"/></span> <span class="input-group-addon"><i class="icon-sonarr-search"/></span>
+5
View File
@@ -0,0 +1,5 @@
var Marionette = require('marionette');
module.exports = Marionette.CompositeView.extend({
template : 'AddMovies/DiscoverEmptyViewTemplate'
});
@@ -0,0 +1,6 @@
<div class="text-center col-md-12">
<h3>
No movies left to discover. Come back at another time :)
</h3>
</div>
@@ -0,0 +1,26 @@
var Backbone = require('backbone');
var MovieModel = require('../Movies/MovieModel');
var _ = require('underscore');
module.exports = Backbone.Collection.extend({
url : function() {
var route = this.action || "";
return window.NzbDrone.ApiRoot + "/movies/discover/" + route;
},
model : MovieModel,
parse : function(response) {
var self = this;
_.each(response, function(model) {
model.id = undefined;
if (self.unmappedFolderModel) {
model.path = self.unmappedFolderModel.get('folder').path;
}
});
return response;
}
});
@@ -2,6 +2,7 @@ var Marionette = require('marionette');
var SearchResultView = require('./SearchResultView'); var SearchResultView = require('./SearchResultView');
var FullMovieCollection = require('../Movies/FullMovieCollection'); var FullMovieCollection = require('../Movies/FullMovieCollection');
var vent = require('vent'); var vent = require('vent');
var $ = require("jquery");
module.exports = Marionette.CollectionView.extend({ module.exports = Marionette.CollectionView.extend({
itemView : SearchResultView, itemView : SearchResultView,
@@ -9,7 +10,7 @@ module.exports = Marionette.CollectionView.extend({
initialize : function(options) { initialize : function(options) {
this.showExisting = true; this.showExisting = true;
this.isExisting = options.isExisting; this.isExisting = options.isExisting;
this.showing = 5; this.showing = 10;
if (this.isExisting) { if (this.isExisting) {
this.showing = 1; this.showing = 1;
} }
@@ -28,9 +29,10 @@ module.exports = Marionette.CollectionView.extend({
}, },
showMore : function() { showMore : function() {
this.showing += 5; var pos = $(window).scrollTop();
this.showing += 10;
this.render(); this.render();
$(window).scrollTop(pos);
return this.showing >= this.collection.length; return this.showing >= this.collection.length;
}, },
+10
View File
@@ -7,6 +7,7 @@ var Profiles = require('../Profile/ProfileCollection');
var RootFolders = require('./RootFolders/RootFolderCollection'); var RootFolders = require('./RootFolders/RootFolderCollection');
var RootFolderLayout = require('./RootFolders/RootFolderLayout'); var RootFolderLayout = require('./RootFolders/RootFolderLayout');
var FullMovieCollection = require('../Movies/FullMovieCollection'); var FullMovieCollection = require('../Movies/FullMovieCollection');
var ImportExclusionModel = require("../Settings/NetImport/ImportExclusionModel");
var Config = require('../Config'); var Config = require('../Config');
var Messenger = require('../Shared/Messenger'); var Messenger = require('../Shared/Messenger');
var AsValidatedView = require('../Mixins/AsValidatedView'); var AsValidatedView = require('../Mixins/AsValidatedView');
@@ -33,6 +34,7 @@ var view = Marionette.ItemView.extend({
events : { events : {
'click .x-add' : '_addWithoutSearch', 'click .x-add' : '_addWithoutSearch',
'click .x-add-search' : '_addAndSearch', 'click .x-add-search' : '_addAndSearch',
"click .x-ignore" : "_ignoreMovie",
'change .x-profile' : '_profileChanged', 'change .x-profile' : '_profileChanged',
'change .x-root-folder' : '_rootFolderChanged', 'change .x-root-folder' : '_rootFolderChanged',
'change .x-season-folder' : '_seasonFolderChanged', 'change .x-season-folder' : '_seasonFolderChanged',
@@ -239,6 +241,14 @@ var view = Marionette.ItemView.extend({
}); });
}, },
_ignoreMovie : function() {
var exclusion = new ImportExclusionModel({tmdbId : this.model.get("tmdbId"),
movieTitle : this.model.get("title"), movieYear : this.model.get("year")});
exclusion.save();
this.model.destroy();
this.remove();
},
_rootFoldersUpdated : function() { _rootFoldersUpdated : function() {
this._configureTemplateHelpers(); this._configureTemplateHelpers();
this.render(); this.render();
@@ -26,7 +26,16 @@
{{#if_eq status compare="inCinemas"}} {{#if_eq status compare="inCinemas"}}
<span class="label label-warning">In Cinemas</span> <span class="label label-warning">In Cinemas</span>
{{/if_eq}} {{/if_eq}}
<span class="label label-default" title="{{ratings.votes}} Vote(s)">{{ratings.value}}</span>
{{#if youTubeTrailerId}}
<span class="label label-info">
<a href="{{youTubeTrailerUrl}}" style="color: white;">Trailer</a>
</span>
{{/if}}
</span> </span>
</h2> </h2>
</div> </div>
</div> </div>
@@ -97,6 +106,10 @@
<button class="btn btn-success add x-add-search" title="Add and Search for movie"> <button class="btn btn-success add x-add-search" title="Add and Search for movie">
<i class="icon-sonarr-search"></i> <i class="icon-sonarr-search"></i>
</button> </button>
<button class="btn btn-warning ignore x-ignore" title="Ignore this movie, so it does not show up anymore">
<i class="icon-sonarr-ignore"></i>
</button>
</div> </div>
</div> </div>
{{else}} {{else}}
+59
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;
}
});
+3
View File
@@ -9,6 +9,9 @@ module.exports = NzbDroneCell.extend({
var title = this.model.get('title'); var title = this.model.get('title');
var infoUrl = this.model.get('infoUrl'); var infoUrl = this.model.get('infoUrl');
var flags = this.model.get("indexerFlags");
if (infoUrl) { if (infoUrl) {
this.$el.html('<a href="{0}">{1}</a>'.format(infoUrl, title)); this.$el.html('<a href="{0}">{1}</a>'.format(infoUrl, title));
} else { } else {
+7
View File
@@ -304,9 +304,16 @@
.fa-icon-color(@brand-danger); .fa-icon-color(@brand-danger);
} }
.icon-sonarr-ignore {
.fa-icon-content(@fa-var-eye-slash);
}
.icon-sonarr-deleted { .icon-sonarr-deleted {
.fa-icon-content(@fa-var-trash); .fa-icon-content(@fa-var-trash);
} }
.icon-sonarr-star {
.fa-icon-content(@fa-var-star);
}
.icon-sonarr-clear { .icon-sonarr-clear {
.fa-icon-content(@fa-var-trash); .fa-icon-content(@fa-var-trash);
+2
View File
@@ -13,6 +13,8 @@
display : block; display : block;
float : none; float : none;
border-radius : @border-radius-base !important; border-radius : @border-radius-base !important;
word-wrap : normal;
white-space : normal;
} }
} }
} }
+3 -3
View File
@@ -168,8 +168,8 @@ module.exports = Marionette.Layout.extend({
}, },
_automaticImport : function (e) { _automaticImport : function (e) {
CommandController.Execute('downloadedMovieScan', { CommandController.Execute('downloadedMoviesScan', {
name : 'downloadedMovieScan', name : 'downloadedMoviesScan',
path : e.folder path : e.folder
}); });
@@ -271,4 +271,4 @@ module.exports = Marionette.Layout.extend({
hideAfter : 5 hideAfter : 5
}); });
} }
}); });
+9 -7
View File
@@ -47,27 +47,29 @@
</div> </div>
</div> </div>
<!--<div class="form-group"> <div class="form-group">
<label class="col-sm-4 control-label">Use Season Folder</label> <label class="col-sm-4 control-label">Static Path</label>
<div class="col-sm-8"> <div class="col-sm-6">
<div class="input-group"> <div class="input-group">
<label class="checkbox toggle well"> <label class="checkbox toggle well">
<input type="checkbox" name="seasonFolder"/> <input type="checkbox" name="pathState"/>
<p> <p>
<span>Yes</span> <span>Yes</span>
<span>No</span> <span>No</span>
</p> </p>
<div class="btn btn-primary slide-button"/> <div class="btn btn-primary slide-button">
</div>
</label> </label>
<span class="help-inline-checkbox"> <span class="help-inline-checkbox">
<i class="icon-sonarr-form-info" title="Should downloaded episodes be stored in season folders?"/> <i class="icon-sonarr-form-info" title="Should movie path stay static or should it change on each disk scan according to your naming config? Note: Auto Rename Folders under Settings -&gt; Media Management must be enabled too."/>
</span> </span>
</div> </div>
</div> </div>
</div>--> </div>
<div class="form-group"> <div class="form-group">
<label class="col-sm-4 control-label">Profile</label> <label class="col-sm-4 control-label">Profile</label>
+13
View File
@@ -22,6 +22,12 @@ var view = Marionette.ItemView.extend({
initialize : function() { initialize : function() {
this.model.set('profiles', Profiles); this.model.set('profiles', Profiles);
var pathState = this.model.get("pathState");
if (pathState == "static") {
this.model.set("pathState", true);
} else {
this.model.set("pathState", false);
}
}, },
onRender : function() { onRender : function() {
@@ -30,11 +36,18 @@ var view = Marionette.ItemView.extend({
model : this.model, model : this.model,
property : 'tags' property : 'tags'
}); });
}, },
_onBeforeSave : function() { _onBeforeSave : function() {
var profileId = this.ui.profile.val(); var profileId = this.ui.profile.val();
this.model.set({ profileId : profileId }); this.model.set({ profileId : profileId });
var pathState = this.model.get("pathState");
if (pathState === true) {
this.model.set("pathState", "static");
} else {
this.model.set("pathState", "dynamic");
}
}, },
_onAfterSave : function() { _onAfterSave : function() {
+7 -12
View File
@@ -15,7 +15,7 @@ module.exports = Marionette.ItemView.extend({
monitored : '.x-monitored', monitored : '.x-monitored',
profile : '.x-profiles', profile : '.x-profiles',
minimumAvailability : '.x-minimumavailability', minimumAvailability : '.x-minimumavailability',
seasonFolder : '.x-season-folder', staticPath : '.x-static-path',
rootFolder : '.x-root-folder', rootFolder : '.x-root-folder',
selectedCount : '.x-selected-count', selectedCount : '.x-selected-count',
container : '.series-editor-footer', container : '.series-editor-footer',
@@ -52,7 +52,6 @@ module.exports = Marionette.ItemView.extend({
this.listenTo(FullMovieCollection, 'save', function() { this.listenTo(FullMovieCollection, 'save', function() {
window.alert(' Done Saving'); window.alert(' Done Saving');
var selected = FullMovieCollection.where({ selected : true }); var selected = FullMovieCollection.where({ selected : true });
}); });
@@ -71,7 +70,7 @@ module.exports = Marionette.ItemView.extend({
var monitored = this.ui.monitored.val(); var monitored = this.ui.monitored.val();
var minAvail = this.ui.minimumAvailability.val(); var minAvail = this.ui.minimumAvailability.val();
var profile = this.ui.profile.val(); var profile = this.ui.profile.val();
var seasonFolder = this.ui.seasonFolder.val(); var staticPath = this.ui.staticPath.val();
var rootFolder = this.ui.rootFolder.val(); var rootFolder = this.ui.rootFolder.val();
var i = 0; var i = 0;
@@ -94,10 +93,8 @@ module.exports = Marionette.ItemView.extend({
model.set('profileId', parseInt(profile, 10)); model.set('profileId', parseInt(profile, 10));
} }
if (seasonFolder === 'true') { if (staticPath !== 'noChange') {
model.set('seasonFolder', true); model.set('pathState', staticPath);
} else if (seasonFolder === 'false') {
model.set('seasonFolder', false);
} }
if (rootFolder !== 'noChange') { if (rootFolder !== 'noChange') {
@@ -129,10 +126,8 @@ module.exports = Marionette.ItemView.extend({
m.set('profileId', parseInt(profile, 10)); m.set('profileId', parseInt(profile, 10));
} }
if (seasonFolder === 'true') { if (staticPath !== 'noChange') {
m.set('seasonFolder', true); m.set('pathState', staticPath);
} else if (seasonFolder === 'false') {
m.set('seasonFolder', false);
} }
if (rootFolder !== 'noChange') { if (rootFolder !== 'noChange') {
@@ -181,7 +176,7 @@ module.exports = Marionette.ItemView.extend({
}, },
_organizeFiles : function() { _organizeFiles : function() {
var selected = this.editorGrid.getSelectedModels(); var selected = FullMovieCollection.where({ selected : true });
var updateFilesMoviesView = new UpdateFilesMoviesView({ movies : selected }); var updateFilesMoviesView = new UpdateFilesMoviesView({ movies : selected });
this.listenToOnce(updateFilesMoviesView, 'updatingFiles', this._afterSave); this.listenToOnce(updateFilesMoviesView, 'updatingFiles', this._afterSave);
@@ -1,6 +1,6 @@
<div class="series-editor-footer"> <div class="series-editor-footer">
<div class="row"> <div class="row">
<div class="form-group col-md-2"> <div class="form-group col-md-1">
<label>Monitored</label> <label>Monitored</label>
<select class="form-control x-action x-monitored"> <select class="form-control x-action x-monitored">
@@ -33,15 +33,15 @@
</select> </select>
</div> </div>
{{!--<div class="form-group col-md-2"> <div class="form-group col-md-2">
<label>Season Folder</label> <label>Static Path</label>
<select class="form-control x-action x-season-folder"> <select class="form-control x-action x-static-path">
<option value="noChange">No change</option> <option value="noChange">No change</option>
<option value="true">Yes</option> <option value="static">Yes</option>
<option value="false">No</option> <option value="dynamic">No</option>
</select> </select>
</div>--}} </div>
<div class="form-group col-md-3"> <div class="form-group col-md-3">
<label>Root Folder</label> <label>Root Folder</label>
@@ -55,7 +55,7 @@
</select> </select>
</div> </div>
<div class="form-group col-md-3 actions"> <div class="form-group col-md-2 actions">
<label class="x-selected-count">0 movies selected</label> <label class="x-selected-count">0 movies selected</label>
<div> <div>
<button class="btn btn-primary x-action x-save">Save</button> <button class="btn btn-primary x-action x-save">Save</button>
+1 -1
View File
@@ -165,7 +165,7 @@ module.exports = Marionette.Layout.extend({
onRender : function() { onRender : function() {
//this._showToolbar(); //this._showToolbar();
//this._showTable(); //this._showTable();
//this._showPager(); //this._showPager();
//if (window.shownOnce){ //if (window.shownOnce){
// this.movieCollection.fetch(); // this.movieCollection.fetch();
//} //}
+1
View File
@@ -48,6 +48,7 @@ var view = Marionette.ItemView.extend({
_onAfterSave : function() { _onAfterSave : function() {
this.trigger('saved'); this.trigger('saved');
vent.trigger(vent.Commands.MovieFileEdited);
vent.trigger(vent.Commands.CloseModalCommand); vent.trigger(vent.Commands.CloseModalCommand);
}, },

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