diff --git a/.DS_Store b/.DS_Store index e7ed6672e..8139cef01 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 6bd416a38..50d8f9832 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -2,4 +2,6 @@ Provide a description of the feature request or bug, the more details the better. -Please use https://forums.sonarr.tv/ for support or other questions. (When in doubt, use the forums) +When possible include a log! + +Please use our [Discord server](https://discord.gg/NWYch8M) for support or longer discussions. diff --git a/.travis.yml b/.travis.yml index f61c413f1..3cea8954a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,3 +10,5 @@ install: after_success: - chmod +x package.sh - ./package.sh +notifications: + - webhooks: https://discordapp.com/api/webhooks/266910310219251712/V-QvCcnYkg3O8PMevcAJOJyCgrYkZQoF2pupLDGbaISNUECmYPd6LRwl3avKHsPyfgWP diff --git a/build.sh b/build.sh index e45c949e9..a3a6eb568 100755 --- a/build.sh +++ b/build.sh @@ -154,8 +154,8 @@ PackageMono() cp $sourceFolder/NzbDrone.Common/CurlSharp.dll.config $outputFolderMono echo "Renaming NzbDrone.Console.exe to NzbDrone.exe" - rm $outputFolderMono/NzbDrone.exe* - for file in $outputFolderMono/NzbDrone.Console.exe*; do + rm $outputFolderMono/Radarr.exe* + for file in $outputFolderMono/Radarr.Console.exe*; do mv "$file" "${file//.Console/}" done @@ -192,8 +192,8 @@ PackageOsxApp() rm -rf $outputFolderOsxApp mkdir $outputFolderOsxApp - cp -r ./osx/Sonarr.app $outputFolderOsxApp - cp -r $outputFolderOsx $outputFolderOsxApp/Sonarr.app/Contents/MacOS + cp -r ./osx/Radarr.app $outputFolderOsxApp + cp -r $outputFolderOsx $outputFolderOsxApp/Radarr.app/Contents/MacOS echo "##teamcity[progressFinish 'Creating OS X App Package']" } diff --git a/osx/Sonarr.app/Contents/Info.plist b/osx/Radarr.app/Contents/Info.plist similarity index 97% rename from osx/Sonarr.app/Contents/Info.plist rename to osx/Radarr.app/Contents/Info.plist index eeae50f41..345002166 100644 --- a/osx/Sonarr.app/Contents/Info.plist +++ b/osx/Radarr.app/Contents/Info.plist @@ -13,7 +13,7 @@ CFBundleExecutable Sonarr CFBundleIconFile - sonarr.icns + radarr.icns CFBundleIdentifier com.osx.sonarr.tv CFBundleInfoDictionaryVersion diff --git a/osx/Radarr.app/Contents/Resources/radarr.icns b/osx/Radarr.app/Contents/Resources/radarr.icns new file mode 100644 index 000000000..5284eec97 Binary files /dev/null and b/osx/Radarr.app/Contents/Resources/radarr.icns differ diff --git a/osx/Sonarr.app/Contents/Resources/sonarr.icns b/osx/Radarr.app/Contents/Resources/sonarr.icns similarity index 100% rename from osx/Sonarr.app/Contents/Resources/sonarr.icns rename to osx/Radarr.app/Contents/Resources/sonarr.icns diff --git a/osx/Sonarr b/osx/Sonarr index db2a35399..bb5d9d6bd 100644 --- a/osx/Sonarr +++ b/osx/Sonarr @@ -4,9 +4,9 @@ DIR=$(cd "$(dirname "$0")"; pwd) #change these values to match your app -EXE_PATH="$DIR/NzbDrone.exe" +EXE_PATH="$DIR/Radarr.exe" APPNAME="Sonarr" - + #set up environment if [[ -x '/opt/local/bin/mono' ]]; then export PATH="/opt/local/bin:$PATH" @@ -29,11 +29,11 @@ export DYLD_FALLBACK_LIBRARY_PATH="$DYLD_FALLBACK_LIBRARY_PATH:$HOME/lib:/usr/lo #mono version check REQUIRED_MAJOR=3 REQUIRED_MINOR=10 - + VERSION_TITLE="Cannot launch $APPNAME" VERSION_MSG="$APPNAME requires Mono Runtime Environment(MRE) $REQUIRED_MAJOR.$REQUIRED_MINOR or later." DOWNLOAD_URL="http://www.mono-project.com/download/#download-mac" - + MONO_VERSION="$(mono --version | grep 'Mono JIT compiler version ' | cut -f5 -d\ )" # if [[ -o DEBUG ]]; then osascript -e "display dialog \"MONO_VERSION: $MONO_VERSION\""; fi @@ -42,7 +42,7 @@ MONO_VERSION_MAJOR="$(echo $MONO_VERSION | cut -f1 -d.)" MONO_VERSION_MINOR="$(echo $MONO_VERSION | cut -f2 -d.)" if [ -z "$MONO_VERSION" ] \ || [ $MONO_VERSION_MAJOR -lt $REQUIRED_MAJOR ] \ - || [ $MONO_VERSION_MAJOR -eq $REQUIRED_MAJOR -a $MONO_VERSION_MINOR -lt $REQUIRED_MINOR ] + || [ $MONO_VERSION_MAJOR -eq $REQUIRED_MAJOR -a $MONO_VERSION_MINOR -lt $REQUIRED_MINOR ] then osascript \ -e "set question to display dialog \"$VERSION_MSG\" with title \"$VERSION_TITLE\" buttons {\"Cancel\", \"Download...\"} default button 2" \ @@ -51,8 +51,8 @@ then echo "$VERSION_MSG" exit 1 fi - + MONO_EXEC="exec mono --debug" - + #run app using mono -$MONO_EXEC "$EXE_PATH" \ No newline at end of file +$MONO_EXEC "$EXE_PATH" diff --git a/package.sh b/package.sh index 65357f083..b754a796d 100644 --- a/package.sh +++ b/package.sh @@ -17,10 +17,10 @@ outputFolderMono='./_output_mono' outputFolderOsx='./_output_osx' outputFolderOsxApp='./_output_osx_app' -tr -d "\r" < $outputFolderOsxApp/Sonarr.app/Contents/MacOS/Sonarr > $outputFolderOsxApp/Sonarr.app/Contents/MacOS/Sonarr2 -rm $outputFolderOsxApp/Sonarr.app/Contents/MacOS/Sonarr -chmod +x $outputFolderOsxApp/Sonarr.app/Contents/MacOS/Sonarr2 -mv $outputFolderOsxApp/Sonarr.app/Contents/MacOS/Sonarr2 $outputFolderOsxApp/Sonarr.app/Contents/MacOS/Sonarr >& error.log +tr -d "\r" < $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr > $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr2 +rm $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr +chmod +x $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr2 +mv $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr2 $outputFolderOsxApp/Radarr.app/Contents/MacOS/Sonarr >& error.log cp -r $outputFolder/ Radarr_Windows_$VERSION cp -r $outputFolderMono/ Radarr_Mono_$VERSION diff --git a/readme.md b/readme.md index c615441d6..00b078e18 100644 --- a/readme.md +++ b/readme.md @@ -3,40 +3,43 @@ This fork of Sonarr aims to turn it into something like Couchpotato. ## Currently working: -* Adding new movies (Note: Movies are currently added as one series with one season and one episode. This will change in the future) +* Adding new movies * Manually searching for releases of movies. * Automatically searching for releases. -* Rarbg.to indexer (Other indexers are coming, I just need to find the right categories) -* Everything that has nothing to do with series from Sonarr should be working as well. +* Automatically importing downloaded movies. +* Recognizing Special Editions, Director's Cut, etc. +* Identifying releases with hardcoded subs. +* Rarbg.to, Torznab and Newznab Indexer. +* QBittorrent and Deluge download client (Other clients are coming) +* New TorrentPotato Indexer (Works well with [Jackett](https://github.com/Jackett/Jackett)) ## Planned Features: * Scanning PreDB to know when a new release is available. -* Fixing the other Indexers. -* Fixing how movies are stored and displayed. +* Fixing the other Indexers and download clients. * Importing of Sonarr config. -* New TorrentPotato Indexer. + +## Download +The latest precompiled binary versions can be found here: https://github.com/galli-leo/Radarr/releases. + +For more up to date versions (but also sometimes broken), daily builds can be found here: +* [OSX](https://leonardogalli.ch/radarr/builds/latest.php?os=osx) +* [Windows](https://leonardogalli.ch/radarr/builds/latest.php?os=windows) +* [Linux](https://leonardogalli.ch/radarr/builds/latest.php?os=mono) ## Major Features Include: ## * Support for major platforms: Windows, Linux, OSX, Raspberry Pi, etc. -* Automatically detects new episodes -* Can scan your existing library and download any missing episodes -* Can watch for better quality of the episodes you already have and do an automatic upgrade. *eg. from DVD to Blu-Ray* +* Can watch for better quality of the movies you have and do an upgrade. * Automatic failed download handling will try another release if one fails -* Manual search so you can pick any release or to see why a release was not downloaded automatically -* Fully configurable episode renaming -* Full integration with SABNzbd and NzbGet -* Full integration with XBMC, Plex (notification, library update, metadata) -* Full support for specials and multi-episode releases +* Manual search so you can pick any release or to see why a release was not downloaded automatically. +* Full integration with SABNzbd and NzbGet. +* Full integration with XBMC, Plex (notification, library update, metadata). * And a beautiful UI -## Download -The latest precompiled binary versions can be found here: https://github.com/galli-leo/Radarr/releases. - ## Configuring Development Environment: ## ### Requirements ### -- Visual Studio 2015 [Free Community Edition](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx) +- Visual Studio 2015 [Free Community Edition](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx) or Mono - [Git](http://git-scm.com/downloads) - [NodeJS](http://nodejs.org/download/) @@ -52,7 +55,7 @@ The latest precompiled binary versions can be found here: https://github.com/gal ### Development ### -- Open `NzbDrone.sln` in Visual Studio +- Open `NzbDrone.sln` in Visual Studio or run the build.sh script, if Mono is installed. - Make sure `NzbDrone.Console` is set as the startup project diff --git a/sonarr.icns b/sonarr.icns new file mode 100644 index 000000000..5284eec97 Binary files /dev/null and b/sonarr.icns differ diff --git a/src/Common/CommonVersionInfo.cs b/src/Common/CommonVersionInfo.cs index d674c376f..f7e96bcb8 100644 --- a/src/Common/CommonVersionInfo.cs +++ b/src/Common/CommonVersionInfo.cs @@ -2,4 +2,4 @@ using System.Reflection; -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] diff --git a/src/NzbDrone.Api.Test/Properties/AssemblyInfo.cs b/src/NzbDrone.Api.Test/Properties/AssemblyInfo.cs index 4d2901c1a..c40806a4c 100644 --- a/src/NzbDrone.Api.Test/Properties/AssemblyInfo.cs +++ b/src/NzbDrone.Api.Test/Properties/AssemblyInfo.cs @@ -21,4 +21,4 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("260b2ff9-d3b7-4d8a-b720-a12c93d045e5")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] diff --git a/src/NzbDrone.Api/Indexers/ReleaseResource.cs b/src/NzbDrone.Api/Indexers/ReleaseResource.cs index 4a8e7e041..291879a44 100644 --- a/src/NzbDrone.Api/Indexers/ReleaseResource.cs +++ b/src/NzbDrone.Api/Indexers/ReleaseResource.cs @@ -24,6 +24,7 @@ namespace NzbDrone.Api.Indexers public string Indexer { get; set; } public string ReleaseGroup { get; set; } public string ReleaseHash { get; set; } + public string Edition { get; set; } public string Title { get; set; } public bool FullSeason { get; set; } public int SeasonNumber { get; set; } @@ -90,6 +91,55 @@ namespace NzbDrone.Api.Indexers if (model.IsForMovie) { downloadAllowed = model.RemoteMovie.DownloadAllowed; + var parsedMovieInfo = model.RemoteMovie.ParsedMovieInfo; + + return new ReleaseResource + { + Guid = releaseInfo.Guid, + Quality = parsedMovieInfo.Quality, + //QualityWeight + Age = releaseInfo.Age, + AgeHours = releaseInfo.AgeHours, + AgeMinutes = releaseInfo.AgeMinutes, + Size = releaseInfo.Size, + IndexerId = releaseInfo.IndexerId, + Indexer = releaseInfo.Indexer, + ReleaseGroup = parsedMovieInfo.ReleaseGroup, + ReleaseHash = parsedMovieInfo.ReleaseHash, + Title = releaseInfo.Title, + FullSeason = parsedMovieInfo.FullSeason, + SeasonNumber = parsedMovieInfo.SeasonNumber, + Language = parsedMovieInfo.Language, + AirDate = "", + SeriesTitle = parsedMovieInfo.MovieTitle, + EpisodeNumbers = new int[0], + AbsoluteEpisodeNumbers = new int[0], + Approved = model.Approved, + TemporarilyRejected = model.TemporarilyRejected, + Rejected = model.Rejected, + TvdbId = releaseInfo.TvdbId, + TvRageId = releaseInfo.TvRageId, + Rejections = model.Rejections.Select(r => r.Reason).ToList(), + PublishDate = releaseInfo.PublishDate, + CommentUrl = releaseInfo.CommentUrl, + DownloadUrl = releaseInfo.DownloadUrl, + InfoUrl = releaseInfo.InfoUrl, + DownloadAllowed = downloadAllowed, + //ReleaseWeight + + MagnetUrl = torrentInfo.MagnetUrl, + InfoHash = torrentInfo.InfoHash, + Seeders = torrentInfo.Seeders, + Leechers = (torrentInfo.Peers.HasValue && torrentInfo.Seeders.HasValue) ? (torrentInfo.Peers.Value - torrentInfo.Seeders.Value) : (int?)null, + Protocol = releaseInfo.DownloadProtocol, + + Edition = parsedMovieInfo.Edition, + + IsDaily = false, + IsAbsoluteNumbering = false, + IsPossibleSpecialEpisode = false, + Special = parsedMovieInfo.Special, + }; } // TODO: Clean this mess up. don't mix data from multiple classes, use sub-resources instead? (Got a huge Deja Vu, didn't we talk about this already once?) diff --git a/src/NzbDrone.Api/Properties/AssemblyInfo.cs b/src/NzbDrone.Api/Properties/AssemblyInfo.cs index 6149a06c4..ea5188e36 100644 --- a/src/NzbDrone.Api/Properties/AssemblyInfo.cs +++ b/src/NzbDrone.Api/Properties/AssemblyInfo.cs @@ -6,6 +6,6 @@ using System.Runtime.InteropServices; [assembly: Guid("4c0922d7-979e-4ff7-b44b-b8ac2100eeb5")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] [assembly: InternalsVisibleTo("NzbDrone.Core")] diff --git a/src/NzbDrone.Api/Series/MovieModule.cs b/src/NzbDrone.Api/Series/MovieModule.cs index 5a8e5f52f..a40695a1c 100644 --- a/src/NzbDrone.Api/Series/MovieModule.cs +++ b/src/NzbDrone.Api/Series/MovieModule.cs @@ -73,7 +73,7 @@ namespace NzbDrone.Api.Movie PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace()); PostValidator.RuleFor(s => s.RootFolderPath).IsValidPath().When(s => s.Path.IsNullOrWhiteSpace()); PostValidator.RuleFor(s => s.Title).NotEmpty(); - PostValidator.RuleFor(s => s.ImdbId).NotNull().NotEmpty().SetValidator(moviesExistsValidator); + PostValidator.RuleFor(s => s.TmdbId).NotNull().NotEmpty().SetValidator(moviesExistsValidator); PutValidator.RuleFor(s => s.Path).IsValidPath(); } diff --git a/src/NzbDrone.Api/Series/MovieResource.cs b/src/NzbDrone.Api/Series/MovieResource.cs index a35b2d210..b1924629d 100644 --- a/src/NzbDrone.Api/Series/MovieResource.cs +++ b/src/NzbDrone.Api/Series/MovieResource.cs @@ -28,6 +28,7 @@ namespace NzbDrone.Api.Movie public string Overview { get; set; } public DateTime? InCinemas { get; set; } public List Images { get; set; } + public string Website { get; set; } public string RemotePoster { get; set; } public int Year { get; set; } @@ -42,6 +43,7 @@ namespace NzbDrone.Api.Movie public DateTime? LastInfoSync { get; set; } public string CleanTitle { get; set; } public string ImdbId { get; set; } + public int TmdbId { get; set; } public string TitleSlug { get; set; } public string RootFolderPath { get; set; } public string Certification { get; set; } @@ -50,6 +52,7 @@ namespace NzbDrone.Api.Movie public DateTime Added { get; set; } public AddMovieOptions AddOptions { get; set; } public Ratings Ratings { get; set; } + public List AlternativeTitles { get; set; } //TODO: Add series statistics as a property of the series (instead of individual properties) @@ -79,7 +82,7 @@ namespace NzbDrone.Api.Movie return new MovieResource { Id = model.Id, - + TmdbId = model.TmdbId, Title = model.Title, //AlternateTitles SortTitle = model.SortTitle, @@ -108,10 +111,12 @@ namespace NzbDrone.Api.Movie TitleSlug = model.TitleSlug, RootFolderPath = model.RootFolderPath, Certification = model.Certification, + Website = model.Website, Genres = model.Genres, Tags = model.Tags, Added = model.Added, AddOptions = model.AddOptions, + AlternativeTitles = model.AlternativeTitles, Ratings = model.Ratings }; } @@ -123,6 +128,7 @@ namespace NzbDrone.Api.Movie return new Core.Tv.Movie { Id = resource.Id, + TmdbId = resource.TmdbId, Title = resource.Title, //AlternateTitles @@ -151,10 +157,12 @@ namespace NzbDrone.Api.Movie TitleSlug = resource.TitleSlug, RootFolderPath = resource.RootFolderPath, Certification = resource.Certification, + Website = resource.Website, Genres = resource.Genres, Tags = resource.Tags, Added = resource.Added, AddOptions = resource.AddOptions, + AlternativeTitles = resource.AlternativeTitles, Ratings = resource.Ratings }; } @@ -162,6 +170,7 @@ namespace NzbDrone.Api.Movie public static Core.Tv.Movie ToModel(this MovieResource resource, Core.Tv.Movie movie) { movie.ImdbId = resource.ImdbId; + movie.TmdbId = resource.TmdbId; movie.Path = resource.Path; movie.ProfileId = resource.ProfileId; diff --git a/src/NzbDrone.Api/System/Tasks/TaskResource.cs b/src/NzbDrone.Api/System/Tasks/TaskResource.cs index fda392cae..d4b583aa5 100644 --- a/src/NzbDrone.Api/System/Tasks/TaskResource.cs +++ b/src/NzbDrone.Api/System/Tasks/TaskResource.cs @@ -7,7 +7,7 @@ namespace NzbDrone.Api.System.Tasks { public string Name { get; set; } public string TaskName { get; set; } - public int Interval { get; set; } + public double Interval { get; set; } public DateTime LastExecution { get; set; } public DateTime NextExecution { get; set; } } diff --git a/src/NzbDrone.App.Test/ContainerFixture.cs b/src/NzbDrone.App.Test/ContainerFixture.cs index 1064d1c5b..e0c01be2f 100644 --- a/src/NzbDrone.App.Test/ContainerFixture.cs +++ b/src/NzbDrone.App.Test/ContainerFixture.cs @@ -8,7 +8,7 @@ using NzbDrone.Core.Jobs; using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; -using NzbDrone.Host; +using Radarr.Host; using NzbDrone.Test.Common; using FluentAssertions; using System.Linq; diff --git a/src/NzbDrone.App.Test/NzbDroneProcessServiceFixture.cs b/src/NzbDrone.App.Test/NzbDroneProcessServiceFixture.cs index 1ee1ee522..dc8eda638 100644 --- a/src/NzbDrone.App.Test/NzbDroneProcessServiceFixture.cs +++ b/src/NzbDrone.App.Test/NzbDroneProcessServiceFixture.cs @@ -3,8 +3,9 @@ using Moq; using NUnit.Framework; using NzbDrone.Common.Model; using NzbDrone.Common.Processes; -using NzbDrone.Host; +using Radarr.Host; using NzbDrone.Test.Common; +using Radarr.Host; namespace NzbDrone.App.Test { diff --git a/src/NzbDrone.App.Test/Properties/AssemblyInfo.cs b/src/NzbDrone.App.Test/Properties/AssemblyInfo.cs index 86a324eef..1ff06bc9b 100644 --- a/src/NzbDrone.App.Test/Properties/AssemblyInfo.cs +++ b/src/NzbDrone.App.Test/Properties/AssemblyInfo.cs @@ -21,4 +21,4 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("b47d34ef-05e8-4826-8a57-9dd05106c964")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] diff --git a/src/NzbDrone.App.Test/RouterTest.cs b/src/NzbDrone.App.Test/RouterTest.cs index 0cf7b6c3d..1805875f0 100644 --- a/src/NzbDrone.App.Test/RouterTest.cs +++ b/src/NzbDrone.App.Test/RouterTest.cs @@ -3,7 +3,7 @@ using Moq; using NUnit.Framework; using NzbDrone.Common; using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Host; +using Radarr.Host; using NzbDrone.Test.Common; namespace NzbDrone.App.Test diff --git a/src/NzbDrone.Automation.Test/Properties/AssemblyInfo.cs b/src/NzbDrone.Automation.Test/Properties/AssemblyInfo.cs index a5d255084..74a352b32 100644 --- a/src/NzbDrone.Automation.Test/Properties/AssemblyInfo.cs +++ b/src/NzbDrone.Automation.Test/Properties/AssemblyInfo.cs @@ -21,4 +21,4 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("6b8945f5-f5b5-4729-865d-f958fbd673d9")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] diff --git a/src/NzbDrone.Common.Test/PathExtensionFixture.cs b/src/NzbDrone.Common.Test/PathExtensionFixture.cs index e3e7fb34a..95e4aebe7 100644 --- a/src/NzbDrone.Common.Test/PathExtensionFixture.cs +++ b/src/NzbDrone.Common.Test/PathExtensionFixture.cs @@ -263,7 +263,7 @@ namespace NzbDrone.Common.Test [Test] public void GetUpdateClientExePath() { - GetIAppDirectoryInfo().GetUpdateClientExePath().Should().BeEquivalentTo(@"C:\Temp\nzbdrone_update\NzbDrone.Update.exe".AsOsAgnostic()); + GetIAppDirectoryInfo().GetUpdateClientExePath().Should().BeEquivalentTo(@"C:\Temp\nzbdrone_update\Radarr.Update.exe".AsOsAgnostic()); } [Test] diff --git a/src/NzbDrone.Common.Test/ServiceFactoryFixture.cs b/src/NzbDrone.Common.Test/ServiceFactoryFixture.cs index 95b5027ff..d8c5d26a4 100644 --- a/src/NzbDrone.Common.Test/ServiceFactoryFixture.cs +++ b/src/NzbDrone.Common.Test/ServiceFactoryFixture.cs @@ -5,7 +5,7 @@ using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Core.Datastore; using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Messaging.Events; -using NzbDrone.Host; +using Radarr.Host; using NzbDrone.Test.Common; namespace NzbDrone.Common.Test diff --git a/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs b/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs index 5c3712d85..bcf03c4ee 100644 --- a/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs +++ b/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs @@ -6,6 +6,8 @@ namespace NzbDrone.Common.Cloud { IHttpRequestBuilderFactory Services { get; } IHttpRequestBuilderFactory SkyHookTvdb { get; } + IHttpRequestBuilderFactory TMDB { get; } + IHttpRequestBuilderFactory TMDBSingle { get; } } public class SonarrCloudRequestBuilder : ISonarrCloudRequestBuilder @@ -18,10 +20,20 @@ namespace NzbDrone.Common.Cloud SkyHookTvdb = new HttpRequestBuilder("http://skyhook.sonarr.tv/v1/tvdb/{route}/{language}/") .SetSegment("language", "en") .CreateFactory(); + + TMDB = new HttpRequestBuilder("https://api.themoviedb.org/3/{route}/{id}{secondaryRoute}") + .AddQueryParam("api_key", "1a7373301961d03f97f853a876dd1212") + .CreateFactory(); + + TMDBSingle = new HttpRequestBuilder("https://api.themoviedb.org/3/{route}") + .AddQueryParam("api_key", "1a7373301961d03f97f853a876dd1212") + .CreateFactory(); } public IHttpRequestBuilderFactory Services { get; private set; } public IHttpRequestBuilderFactory SkyHookTvdb { get; private set; } + public IHttpRequestBuilderFactory TMDB { get; private set; } + public IHttpRequestBuilderFactory TMDBSingle { get; private set; } } } diff --git a/src/NzbDrone.Common/Extensions/PathExtensions.cs b/src/NzbDrone.Common/Extensions/PathExtensions.cs index 7e77f9d7e..893706f3d 100644 --- a/src/NzbDrone.Common/Extensions/PathExtensions.cs +++ b/src/NzbDrone.Common/Extensions/PathExtensions.cs @@ -13,7 +13,7 @@ namespace NzbDrone.Common.Extensions private const string NZBDRONE_DB = "nzbdrone.db"; private const string NZBDRONE_LOG_DB = "logs.db"; private const string NLOG_CONFIG_FILE = "nlog.config"; - private const string UPDATE_CLIENT_EXE = "NzbDrone.Update.exe"; + private const string UPDATE_CLIENT_EXE = "Radarr.Update.exe"; private const string BACKUP_FOLDER = "Backups"; private static readonly string UPDATE_SANDBOX_FOLDER_NAME = "nzbdrone_update" + Path.DirectorySeparatorChar; diff --git a/src/NzbDrone.Common/Processes/ProcessProvider.cs b/src/NzbDrone.Common/Processes/ProcessProvider.cs index 57068c840..50cc89a84 100644 --- a/src/NzbDrone.Common/Processes/ProcessProvider.cs +++ b/src/NzbDrone.Common/Processes/ProcessProvider.cs @@ -35,8 +35,8 @@ namespace NzbDrone.Common.Processes { private readonly Logger _logger; - public const string NZB_DRONE_PROCESS_NAME = "NzbDrone"; - public const string NZB_DRONE_CONSOLE_PROCESS_NAME = "NzbDrone.Console"; + public const string NZB_DRONE_PROCESS_NAME = "Radarr"; + public const string NZB_DRONE_CONSOLE_PROCESS_NAME = "Radarr.Console"; public ProcessProvider(Logger logger) { diff --git a/src/NzbDrone.Common/Properties/AssemblyInfo.cs b/src/NzbDrone.Common/Properties/AssemblyInfo.cs index e8cdf90c1..89cc0d8c9 100644 --- a/src/NzbDrone.Common/Properties/AssemblyInfo.cs +++ b/src/NzbDrone.Common/Properties/AssemblyInfo.cs @@ -9,4 +9,4 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("b6eaa144-e13b-42e5-a738-c60d89c0f728")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] diff --git a/src/NzbDrone.Console/ConsoleAlerts.cs b/src/NzbDrone.Console/ConsoleAlerts.cs index 4d623fc8e..8533c46f2 100644 --- a/src/NzbDrone.Console/ConsoleAlerts.cs +++ b/src/NzbDrone.Console/ConsoleAlerts.cs @@ -1,4 +1,4 @@ -using NzbDrone.Host; +using Radarr.Host; namespace NzbDrone.Console { diff --git a/src/NzbDrone.Console/ConsoleApp.cs b/src/NzbDrone.Console/ConsoleApp.cs index 83040c67c..eb75bddc7 100644 --- a/src/NzbDrone.Console/ConsoleApp.cs +++ b/src/NzbDrone.Console/ConsoleApp.cs @@ -3,7 +3,7 @@ using System.Net.Sockets; using NLog; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Instrumentation; -using NzbDrone.Host; +using Radarr.Host; namespace NzbDrone.Console { diff --git a/src/NzbDrone.Console/NzbDrone.Console.csproj b/src/NzbDrone.Console/NzbDrone.Console.csproj index 61cc3190f..a1a04c94f 100644 --- a/src/NzbDrone.Console/NzbDrone.Console.csproj +++ b/src/NzbDrone.Console/NzbDrone.Console.csproj @@ -9,7 +9,7 @@ Exe Properties NzbDrone.Console - NzbDrone.Console + Radarr.Console v4.0 512 diff --git a/src/NzbDrone.Console/Properties/AssemblyInfo.cs b/src/NzbDrone.Console/Properties/AssemblyInfo.cs index ed519f028..358a6bcb4 100644 --- a/src/NzbDrone.Console/Properties/AssemblyInfo.cs +++ b/src/NzbDrone.Console/Properties/AssemblyInfo.cs @@ -8,4 +8,4 @@ using System.Runtime.InteropServices; [assembly: AssemblyTitle("NzbDrone.Host")] [assembly: Guid("67AADCD9-89AA-4D95-8281-3193740E70E5")] -[assembly: AssemblyVersion("10.0.0.*")] \ No newline at end of file +[assembly: AssemblyVersion("0.1.0.*")] \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/Properties/AssemblyInfo.cs b/src/NzbDrone.Core.Test/Properties/AssemblyInfo.cs index fca9cdaa2..e2fe85370 100644 --- a/src/NzbDrone.Core.Test/Properties/AssemblyInfo.cs +++ b/src/NzbDrone.Core.Test/Properties/AssemblyInfo.cs @@ -25,6 +25,6 @@ using System.Runtime.InteropServices; [assembly: Guid("699aed1b-015e-4f0d-9c81-d5557b05d260")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] [assembly: InternalsVisibleTo("NzbDrone.Core")] \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs b/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs index ef29fe797..5a7bf569b 100644 --- a/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs +++ b/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs @@ -59,7 +59,7 @@ namespace NzbDrone.Core.Test.UpdateTests Mocker.GetMock().Setup(c => c.Verify(It.IsAny(), It.IsAny())).Returns(true); Mocker.GetMock().Setup(c => c.GetCurrentProcess()).Returns(new ProcessInfo { Id = 12 }); - Mocker.GetMock().Setup(c => c.ExecutingApplication).Returns(@"C:\Test\NzbDrone.exe"); + Mocker.GetMock().Setup(c => c.ExecutingApplication).Returns(@"C:\Test\Radarr.exe"); Mocker.GetMock() .SetupGet(s => s.UpdateAutomatically) diff --git a/src/NzbDrone.Core/Datastore/Migration/106_add_tmdb_stuff.cs b/src/NzbDrone.Core/Datastore/Migration/106_add_tmdb_stuff.cs new file mode 100644 index 000000000..106dcdbd1 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/106_add_tmdb_stuff.cs @@ -0,0 +1,21 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(106)] + public class add_tmdb_stuff : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("Movies") + .AddColumn("TmdbId").AsInt32().WithDefaultValue(0); + Alter.Table("Movies") + .AddColumn("Website").AsString().Nullable(); + Alter.Table("Movies") + .AlterColumn("ImdbId").AsString().Nullable(); + Alter.Table("Movies") + .AddColumn("AlternativeTitles").AsString().Nullable(); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/Migration/107_fix_movie_files.cs b/src/NzbDrone.Core/Datastore/Migration/107_fix_movie_files.cs new file mode 100644 index 000000000..d1b82b862 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/107_fix_movie_files.cs @@ -0,0 +1,14 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(107)] + public class fix_movie_files : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("MovieFiles").AlterColumn("Path").AsString().Nullable(); //Should be deleted, but to much work, ¯\_(ツ)_/¯ + } + } +} diff --git a/src/NzbDrone.Core/Datastore/Migration/108_update_schedule_interval.cs b/src/NzbDrone.Core/Datastore/Migration/108_update_schedule_interval.cs new file mode 100644 index 000000000..82f204b3e --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/108_update_schedule_interval.cs @@ -0,0 +1,15 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(108)] + public class update_schedule_intervale : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("ScheduledTasks").AlterColumn("Interval").AsDouble(); + Execute.Sql("UPDATE ScheduledTasks SET Interval=0.25 WHERE TypeName='NzbDrone.Core.Download.CheckForFinishedDownloadCommand'"); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index 65a82948b..30c0b038f 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -140,6 +140,7 @@ namespace NzbDrone.Core.Datastore RegisterEmbeddedConverter(); RegisterProviderSettingConverter(); + MapRepository.Instance.RegisterTypeConverter(typeof(int), new Int32Converter()); MapRepository.Instance.RegisterTypeConverter(typeof(double), new DoubleConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(DateTime), new UtcConverter()); diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs index af071b15d..e2d171ee4 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs @@ -66,9 +66,9 @@ namespace NzbDrone.Core.DecisionEngine try { - var parsedEpisodeInfo = Parser.Parser.ParseTitle(report.Title); + var parsedEpisodeInfo = Parser.Parser.ParseMovieTitle(report.Title); - if (parsedEpisodeInfo != null && !parsedEpisodeInfo.SeriesTitle.IsNullOrWhiteSpace()) + if (parsedEpisodeInfo != null && !parsedEpisodeInfo.MovieTitle.IsNullOrWhiteSpace()) { RemoteMovie remoteEpisode = _parsingService.Map(parsedEpisodeInfo, "", searchCriteria); remoteEpisode.Release = report; @@ -81,9 +81,18 @@ namespace NzbDrone.Core.DecisionEngine } else { - remoteEpisode.DownloadAllowed = true; - //decision = GetDecisionForReport(remoteEpisode, searchCriteria); TODO: Rewrite this for movies! - decision = new DownloadDecision(remoteEpisode); + if (parsedEpisodeInfo.Quality.HardcodedSubs.IsNotNullOrWhiteSpace()) + { + remoteEpisode.DownloadAllowed = true; + decision = new DownloadDecision(remoteEpisode, new Rejection("Hardcoded subs found: " + parsedEpisodeInfo.Quality.HardcodedSubs)); + } + else + { + remoteEpisode.DownloadAllowed = true; + decision = GetDecisionForReport(remoteEpisode, searchCriteria); + //decision = new DownloadDecision(remoteEpisode); + } + } } } @@ -196,6 +205,14 @@ namespace NzbDrone.Core.DecisionEngine } } + private DownloadDecision GetDecisionForReport(RemoteMovie remoteEpisode, SearchCriteriaBase searchCriteria = null) + { + var reasons = _specifications.Select(c => EvaluateSpec(c, remoteEpisode, searchCriteria)) + .Where(c => c != null); + + return new DownloadDecision(remoteEpisode, reasons.ToArray()); + } + private DownloadDecision GetDecisionForReport(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria = null) { var reasons = _specifications.Select(c => EvaluateSpec(c, remoteEpisode, searchCriteria)) @@ -226,5 +243,32 @@ namespace NzbDrone.Core.DecisionEngine return null; } + + private Rejection EvaluateSpec(IDecisionEngineSpecification spec, RemoteMovie remoteEpisode, SearchCriteriaBase searchCriteriaBase = null) + { + try + { + var result = spec.IsSatisfiedBy(remoteEpisode, searchCriteriaBase); + + if (!result.Accepted) + { + return new Rejection(result.Reason, spec.Type); + } + } + catch (NotImplementedException e) + { + _logger.Info("Spec " + spec.GetType().Name + " does not care about movies."); + } + catch (Exception e) + { + e.Data.Add("report", remoteEpisode.Release.ToJson()); + e.Data.Add("parsed", remoteEpisode.ParsedEpisodeInfo.ToJson()); + _logger.Error(e, "Couldn't evaluate decision on " + remoteEpisode.Release.Title + ", with spec: " + spec.GetType().Name); + return new Rejection(string.Format("{0}: {1}", spec.GetType().Name, e.Message));//TODO UPDATE SPECS! + return null; + } + + return null; + } } } diff --git a/src/NzbDrone.Core/DecisionEngine/IDecisionEngineSpecification.cs b/src/NzbDrone.Core/DecisionEngine/IDecisionEngineSpecification.cs index 199984734..e98a6977f 100644 --- a/src/NzbDrone.Core/DecisionEngine/IDecisionEngineSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/IDecisionEngineSpecification.cs @@ -8,5 +8,7 @@ namespace NzbDrone.Core.DecisionEngine RejectionType Type { get; } Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria); + + Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria); } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs index 4ab566d2e..e4d771cae 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs @@ -107,5 +107,58 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger.Debug("Item: {0}, meets size constraints.", subject); return Decision.Accept(); } + + public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + _logger.Debug("Beginning size check for: {0}", subject); + + var quality = subject.ParsedMovieInfo.Quality.Quality; + + if (subject.Release.Size == 0) + { + _logger.Debug("Release has unknown size, skipping size check."); + return Decision.Accept(); + } + + var qualityDefinition = _qualityDefinitionService.Get(quality); + if (qualityDefinition.MinSize.HasValue) + { + var minSize = qualityDefinition.MinSize.Value.Megabytes(); + + //Multiply maxSize by Series.Runtime + minSize = minSize * subject.Movie.Runtime; + + //If the parsed size is smaller than minSize we don't want it + if (subject.Release.Size < minSize) + { + var runtimeMessage = subject.Movie.Title; + + _logger.Debug("Item: {0}, Size: {1} is smaller than minimum allowed size ({2} bytes for {3}), rejecting.", subject, subject.Release.Size, minSize, runtimeMessage); + return Decision.Reject("{0} is smaller than minimum allowed {1} (for {2})", subject.Release.Size.SizeSuffix(), minSize.SizeSuffix(), runtimeMessage); + } + } + if (!qualityDefinition.MaxSize.HasValue || qualityDefinition.MaxSize.Value == 0) + { + _logger.Debug("Max size is unlimited - skipping check."); + } + else + { + var maxSize = qualityDefinition.MaxSize.Value.Megabytes(); + + //Multiply maxSize by Series.Runtime + maxSize = maxSize * subject.Movie.Runtime; + + //If the parsed size is greater than maxSize we don't want it + if (subject.Release.Size > maxSize) + {; + + _logger.Debug("Item: {0}, Size: {1} is greater than maximum allowed size ({2} for {3}), rejecting.", subject, subject.Release.Size, maxSize, subject.Movie.Title); + return Decision.Reject("{0} is larger than maximum allowed {1} (for {2})", subject.Release.Size.SizeSuffix(), maxSize.SizeSuffix(), subject.Movie.Title); + } + } + + _logger.Debug("Item: {0}, meets size constraints.", subject); + return Decision.Accept(); + } } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/AnimeVersionUpgradeSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/AnimeVersionUpgradeSpecification.cs index c2f93f7c0..33f6cff9f 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/AnimeVersionUpgradeSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/AnimeVersionUpgradeSpecification.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using NLog; using NzbDrone.Common.Extensions; @@ -55,5 +56,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return Decision.Accept(); } + + public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + throw new NotImplementedException(); + } } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs index 18b216263..82bc2d83c 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs @@ -2,6 +2,7 @@ using NLog; using NzbDrone.Core.Blacklisting; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; +using System; namespace NzbDrone.Core.DecisionEngine.Specifications { @@ -28,5 +29,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return Decision.Accept(); } + + public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + + throw new NotImplementedException(); + + return Decision.Accept(); + } } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs index 6dfdbc64c..1ac882632 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using NLog; using NzbDrone.Core.IndexerSearch.Definitions; @@ -34,5 +35,18 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return Decision.Accept(); } + + public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + if (subject.Movie.MovieFile.Value != null) + { + if (!_qualityUpgradableSpecification.CutoffNotMet(subject.Movie.Profile, subject.Movie.MovieFile.Value.Quality, subject.ParsedMovieInfo.Quality)) + { + return Decision.Reject("Existing file meets cutoff: {0}", subject.Movie.Profile.Value.Cutoff); + } + } + + return Decision.Accept(); + } } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/FullSeasonSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/FullSeasonSpecification.cs index c2d86afe8..7d2f89d26 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/FullSeasonSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/FullSeasonSpecification.cs @@ -36,5 +36,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return Decision.Accept(); } + + public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + throw new NotImplementedException(); + } } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs index 9f7f75038..fe244d057 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs @@ -29,5 +29,20 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return Decision.Accept(); } + + public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + var wantedLanguage = subject.Movie.Profile.Value.Language; + + _logger.Debug("Checking if report meets language requirements. {0}", subject.ParsedMovieInfo.Language); + + if (subject.ParsedMovieInfo.Language != wantedLanguage) + { + _logger.Debug("Report Language: {0} rejected because it is not wanted, wanted {1}", subject.ParsedMovieInfo.Language, wantedLanguage); + return Decision.Reject("{0} is wanted, but found {1}", wantedLanguage, subject.ParsedMovieInfo.Language); + } + + return Decision.Accept(); + } } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/MinimumAgeSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/MinimumAgeSpecification.cs index 449d7be76..bcec8d6cd 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/MinimumAgeSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/MinimumAgeSpecification.cs @@ -36,6 +36,37 @@ namespace NzbDrone.Core.DecisionEngine.Specifications } + _logger.Debug("Checking if report meets minimum age requirements. {0}", age); + + if (age < minimumAge) + { + _logger.Debug("Only {0} minutes old, minimum age is {1} minutes", age, minimumAge); + return Decision.Reject("Only {0} minutes old, minimum age is {1} minutes", age, minimumAge); + } + + _logger.Debug("Release is {0} minutes old, greater than minimum age of {1} minutes", age, minimumAge); + + return Decision.Accept(); + } + + public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + if (subject.Release.DownloadProtocol != Indexers.DownloadProtocol.Usenet) + { + _logger.Debug("Not checking minimum age requirement for non-usenet report"); + return Decision.Accept(); + } + + var age = subject.Release.AgeMinutes; + var minimumAge = _configService.MinimumAge; + + if (minimumAge == 0) + { + _logger.Debug("Minimum age is not set."); + return Decision.Accept(); + } + + _logger.Debug("Checking if report meets minimum age requirements. {0}", age); if (age < minimumAge) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs index 02ff7653a..eba2566e2 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs @@ -1,4 +1,5 @@ -using NLog; +using System; +using NLog; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -25,5 +26,16 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return Decision.Accept(); } + + public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + if (subject.Release.Title.ToLower().Contains("sample") && subject.Release.Size < 70.Megabytes()) + { + _logger.Debug("Sample release, rejecting."); + return Decision.Reject("Sample"); + } + + return Decision.Accept(); + } } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/ProtocolSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/ProtocolSpecification.cs index 008e58812..93846789c 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/ProtocolSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/ProtocolSpecification.cs @@ -38,5 +38,24 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return Decision.Accept(); } + + public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + var delayProfile = _delayProfileService.BestForTags(subject.Movie.Tags); + + if (subject.Release.DownloadProtocol == DownloadProtocol.Usenet && !delayProfile.EnableUsenet) + { + _logger.Debug("[{0}] Usenet is not enabled for this series", subject.Release.Title); + return Decision.Reject("Usenet is not enabled for this series"); + } + + if (subject.Release.DownloadProtocol == DownloadProtocol.Torrent && !delayProfile.EnableTorrent) + { + _logger.Debug("[{0}] Torrent is not enabled for this series", subject.Release.Title); + return Decision.Reject("Torrent is not enabled for this series"); + } + + return Decision.Accept(); + } } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs index 7913e0e7e..dcba1ef41 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs @@ -26,5 +26,17 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return Decision.Accept(); } + + public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + _logger.Debug("Checking if report meets quality requirements. {0}", subject.ParsedMovieInfo.Quality); + if (!subject.Movie.Profile.Value.Items.Exists(v => v.Allowed && v.Quality == subject.ParsedMovieInfo.Quality.Quality)) + { + _logger.Debug("Quality {0} rejected by Series' quality profile", subject.ParsedMovieInfo.Quality); + return Decision.Reject("{0} is not wanted in profile", subject.ParsedMovieInfo.Quality.Quality); + } + + return Decision.Accept(); + } } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs index 6f3ec1bea..8ff6105d7 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs @@ -50,5 +50,32 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return Decision.Accept(); } + + public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + var queue = _queueService.GetQueue() + .Select(q => q.RemoteMovie).ToList(); + + var matchingSeries = queue.Where(q => q.Movie.Id == subject.Movie.Id); + + foreach (var remoteEpisode in matchingSeries) + { + _logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0}", remoteEpisode.ParsedEpisodeInfo.Quality); + + if (!_qualityUpgradableSpecification.CutoffNotMet(subject.Movie.Profile, remoteEpisode.ParsedMovieInfo.Quality, subject.ParsedMovieInfo.Quality)) + { + return Decision.Reject("Quality for release in queue already meets cutoff: {0}", remoteEpisode.ParsedEpisodeInfo.Quality); + } + + _logger.Debug("Checking if release is higher quality than queued release. Queued quality is: {0}", remoteEpisode.ParsedMovieInfo.Quality); + + if (!_qualityUpgradableSpecification.IsUpgradable(subject.Movie.Profile, remoteEpisode.ParsedMovieInfo.Quality, subject.ParsedMovieInfo.Quality)) + { + return Decision.Reject("Quality for release in queue is of equal or higher preference: {0}", remoteEpisode.ParsedMovieInfo.Quality); + } + } + + return Decision.Accept(); + } } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs index 7f278cb7e..7a11bacfe 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs @@ -42,5 +42,27 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return Decision.Accept(); } + + public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + if (subject.Release == null || subject.Release.Container.IsNullOrWhiteSpace()) + { + return Decision.Accept(); + } + + if (_dvdContainerTypes.Contains(subject.Release.Container.ToLower())) + { + _logger.Debug("Release contains raw DVD, rejecting."); + return Decision.Reject("Raw DVD release"); + } + + if (_blurayContainerTypes.Contains(subject.Release.Container.ToLower())) + { + _logger.Debug("Release contains raw Bluray, rejecting."); + return Decision.Reject("Raw Bluray release"); + } + + return Decision.Accept(); + } } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs index 9fb8c13f5..42735995f 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs @@ -62,6 +62,46 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return Decision.Accept(); } + public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + _logger.Debug("Checking if release meets restrictions: {0}", subject); + + var title = subject.Release.Title; + var restrictions = _restrictionService.AllForTags(subject.Movie.Tags); + + var required = restrictions.Where(r => r.Required.IsNotNullOrWhiteSpace()); + var ignored = restrictions.Where(r => r.Ignored.IsNotNullOrWhiteSpace()); + + foreach (var r in required) + { + var requiredTerms = r.Required.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); + + var foundTerms = ContainsAny(requiredTerms, title); + if (foundTerms.Empty()) + { + var terms = string.Join(", ", requiredTerms); + _logger.Debug("[{0}] does not contain one of the required terms: {1}", title, terms); + return Decision.Reject("Does not contain one of the required terms: {0}", terms); + } + } + + foreach (var r in ignored) + { + var ignoredTerms = r.Ignored.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); + + var foundTerms = ContainsAny(ignoredTerms, title); + if (foundTerms.Any()) + { + var terms = string.Join(", ", foundTerms); + _logger.Debug("[{0}] contains these ignored terms: {1}", title, terms); + return Decision.Reject("Contains these ignored terms: {0}", terms); + } + } + + _logger.Debug("[{0}] No restrictions apply, allowing", subject); + return Decision.Accept(); + } + private static List ContainsAny(List terms, string title) { return terms.Where(t => title.ToLowerInvariant().Contains(t.ToLowerInvariant())).ToList(); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs index 97802f871..7663c3fb1 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs @@ -38,5 +38,26 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return Decision.Accept(); } + + public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + if (subject.Release.DownloadProtocol != Indexers.DownloadProtocol.Usenet) + { + _logger.Debug("Not checking retention requirement for non-usenet report"); + return Decision.Accept(); + } + + var age = subject.Release.Age; + var retention = _configService.Retention; + + _logger.Debug("Checking if report meets retention requirements. {0}", age); + if (retention > 0 && age > retention) + { + _logger.Debug("Report age: {0} rejected by user's retention limit", age); + return Decision.Reject("Older than configured retention"); + } + + return Decision.Accept(); + } } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs index 68551c66c..f75ac84af 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs @@ -28,6 +28,71 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync public RejectionType Type => RejectionType.Temporary; + public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + if (searchCriteria != null && searchCriteria.UserInvokedSearch) + { + _logger.Debug("Ignoring delay for user invoked search"); + return Decision.Accept(); + } + + var profile = subject.Movie.Profile.Value; + var delayProfile = _delayProfileService.BestForTags(subject.Movie.Tags); + var delay = delayProfile.GetProtocolDelay(subject.Release.DownloadProtocol); + var isPreferredProtocol = subject.Release.DownloadProtocol == delayProfile.PreferredProtocol; + + if (delay == 0) + { + _logger.Debug("Profile does not require a waiting period before download for {0}.", subject.Release.DownloadProtocol); + return Decision.Accept(); + } + + var comparer = new QualityModelComparer(profile); + + if (isPreferredProtocol) + { + var upgradable = _qualityUpgradableSpecification.IsUpgradable(profile, subject.Movie.MovieFile.Value.Quality, subject.ParsedMovieInfo.Quality); + + if (upgradable) + { + var revisionUpgrade = _qualityUpgradableSpecification.IsRevisionUpgrade(subject.Movie.MovieFile.Value.Quality, subject.ParsedMovieInfo.Quality); + + if (revisionUpgrade) + { + _logger.Debug("New quality is a better revision for existing quality, skipping delay"); + return Decision.Accept(); + } + } + + } + + // If quality meets or exceeds the best allowed quality in the profile accept it immediately + var bestQualityInProfile = new QualityModel(profile.LastAllowedQuality()); + var isBestInProfile = comparer.Compare(subject.ParsedEpisodeInfo.Quality, bestQualityInProfile) >= 0; + + if (isBestInProfile && isPreferredProtocol) + { + _logger.Debug("Quality is highest in profile for preferred protocol, will not delay"); + return Decision.Accept(); + } + + /* + var oldest = _pendingReleaseService.OldestPendingRelease(subject.Series.Id, episodeIds); + + if (oldest != null && oldest.Release.AgeMinutes > delay) + { + return Decision.Accept(); + } + + if (subject.Release.AgeMinutes < delay) + { + _logger.Debug("Waiting for better quality release, There is a {0} minute delay on {1}", delay, subject.Release.DownloadProtocol); + return Decision.Reject("Waiting for better quality release"); + }*/ //TODO: Update for movies! + + return Decision.Accept(); + } + public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { if (searchCriteria != null && searchCriteria.UserInvokedSearch) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs index 9aa4fabf1..2a312fbdd 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs @@ -28,6 +28,56 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync public RejectionType Type => RejectionType.Permanent; + public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + if (searchCriteria != null) + { + _logger.Debug("Skipping history check during search"); + return Decision.Accept(); + } + + var cdhEnabled = _configService.EnableCompletedDownloadHandling; + + _logger.Debug("Performing history status check on report"); + _logger.Debug("Checking current status of episode [{0}] in history", subject.Movie.Id); + var mostRecent = _historyService.MostRecentForMovie(subject.Movie.Id); + + if (mostRecent != null && mostRecent.EventType == HistoryEventType.Grabbed) + { + var recent = mostRecent.Date.After(DateTime.UtcNow.AddHours(-12)); + var cutoffUnmet = _qualityUpgradableSpecification.CutoffNotMet(subject.Movie.Profile, mostRecent.Quality, subject.ParsedMovieInfo.Quality); + var upgradeable = _qualityUpgradableSpecification.IsUpgradable(subject.Movie.Profile, mostRecent.Quality, subject.ParsedMovieInfo.Quality); + + if (!recent && cdhEnabled) + { + return Decision.Accept(); + } + + if (!cutoffUnmet) + { + if (recent) + { + return Decision.Reject("Recent grab event in history already meets cutoff: {0}", mostRecent.Quality); + } + + return Decision.Reject("CDH is disabled and grab event in history already meets cutoff: {0}", mostRecent.Quality); + } + + if (!upgradeable) + { + if (recent) + { + return Decision.Reject("Recent grab event in history is of equal or higher quality: {0}", mostRecent.Quality); + } + + return Decision.Reject("CDH is disabled and grab event in history is of equal or higher quality: {0}", mostRecent.Quality); + } + } + + + return Decision.Accept(); + } + public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { if (searchCriteria != null) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/MonitoredEpisodeSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/MonitoredEpisodeSpecification.cs index f56f26478..ccb87c414 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/MonitoredEpisodeSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/MonitoredEpisodeSpecification.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using NLog; using NzbDrone.Core.IndexerSearch.Definitions; @@ -16,6 +17,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync public RejectionType Type => RejectionType.Permanent; + public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + throw new NotImplementedException(); + } + public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { if (searchCriteria != null) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs index 0c6632d25..d029e929c 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs @@ -47,6 +47,39 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync } } + return Decision.Accept(); + } + + public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + if (searchCriteria != null) + { + return Decision.Accept(); + } + + if (subject.Movie.MovieFile.Value == null) + { + return Decision.Accept(); + } + + var file = subject.Movie.MovieFile.Value; + + if (_qualityUpgradableSpecification.IsRevisionUpgrade(file.Quality, subject.ParsedMovieInfo.Quality)) + { + if (file.DateAdded < DateTime.Today.AddDays(-7)) + { + _logger.Debug("Proper for old file, rejecting: {0}", subject); + return Decision.Reject("Proper for old file"); + } + + if (!_configService.AutoDownloadPropers) + { + _logger.Debug("Auto downloading of propers is disabled"); + return Decision.Reject("Proper downloading is disabled"); + } + } + + return Decision.Accept(); } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/SameEpisodesGrabSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/SameEpisodesGrabSpecification.cs index 1a8c5db5b..bd3c2f908 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/SameEpisodesGrabSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/SameEpisodesGrabSpecification.cs @@ -1,3 +1,4 @@ +using System; using NLog; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -27,5 +28,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger.Debug("Episode file on disk contains more episodes than this release contains"); return Decision.Reject("Episode file on disk contains more episodes than this release contains"); } + + public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + throw new NotImplementedException(); + } } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs index 50fd9b3cc..285dc5b5e 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs @@ -1,3 +1,4 @@ +using System; using NLog; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -39,5 +40,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search return Decision.Accept(); } + + public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/EpisodeRequestedSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/EpisodeRequestedSpecification.cs index 60640442f..d8c0065a5 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/EpisodeRequestedSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/EpisodeRequestedSpecification.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using NLog; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -17,6 +18,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search public RejectionType Type => RejectionType.Permanent; + public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + throw new NotImplementedException(); + } + public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria) { if (searchCriteria == null) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeasonMatchSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeasonMatchSpecification.cs index 56e986e19..c7f96303d 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeasonMatchSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeasonMatchSpecification.cs @@ -1,3 +1,4 @@ +using System; using NLog; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -15,6 +16,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search public RejectionType Type => RejectionType.Permanent; + public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + throw new NotImplementedException(); + } + public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria) { if (searchCriteria == null) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeriesSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeriesSpecification.cs index 7f1201b33..dde54155f 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeriesSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeriesSpecification.cs @@ -32,5 +32,23 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search return Decision.Accept(); } + + public Decision IsSatisfiedBy(RemoteMovie remoteEpisode, SearchCriteriaBase searchCriteria) + { + if (searchCriteria == null) + { + return Decision.Accept(); + } + + _logger.Debug("Checking if movie matches searched movie"); + + if (remoteEpisode.Movie.Id != searchCriteria.Movie.Id) + { + _logger.Debug("Series {0} does not match {1}", remoteEpisode.Movie, searchCriteria.Series); + return Decision.Reject("Wrong movie"); + } + + return Decision.Accept(); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleEpisodeSearchMatchSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleEpisodeSearchMatchSpecification.cs index dfaa711ff..18162d3f5 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleEpisodeSearchMatchSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleEpisodeSearchMatchSpecification.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using NLog; using NzbDrone.Core.IndexerSearch.Definitions; @@ -16,6 +17,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search public RejectionType Type => RejectionType.Permanent; + public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + throw new NotImplementedException(); + } + public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria) { if (searchCriteria == null) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/TorrentSeedingSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/TorrentSeedingSpecification.cs index 87c244b53..142e2009a 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/TorrentSeedingSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/TorrentSeedingSpecification.cs @@ -33,5 +33,23 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search return Decision.Accept(); } + + public Decision IsSatisfiedBy(RemoteMovie remoteEpisode, SearchCriteriaBase searchCriteria) + { + var torrentInfo = remoteEpisode.Release as TorrentInfo; + + if (torrentInfo == null) + { + return Decision.Accept(); + } + + if (torrentInfo.Seeders != null && torrentInfo.Seeders < 1) + { + _logger.Debug("Not enough seeders. ({0})", torrentInfo.Seeders); + return Decision.Reject("Not enough seeders. ({0})", torrentInfo.Seeders); + } + + return Decision.Accept(); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs index 5a24b6305..e30d0fc92 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs @@ -30,6 +30,25 @@ namespace NzbDrone.Core.DecisionEngine.Specifications } } + return Decision.Accept(); + } + + public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + if (subject.Movie.MovieFile.Value == null) + { + return Decision.Accept(); + } + + var file = subject.Movie.MovieFile.Value; + _logger.Debug("Comparing file quality with report. Existing file is {0}", file.Quality); + + if (!_qualityUpgradableSpecification.IsUpgradable(subject.Movie.Profile, file.Quality, subject.ParsedMovieInfo.Quality)) + { + return Decision.Reject("Quality for existing file on disk is of equal or higher preference: {0}", file.Quality); + } + + return Decision.Accept(); } } diff --git a/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackhole.cs b/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackhole.cs index 8a82fe93a..202e008c0 100644 --- a/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackhole.cs +++ b/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackhole.cs @@ -32,7 +32,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole ScanGracePeriod = TimeSpan.FromSeconds(30); } - protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContent) + protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContents) { var title = remoteEpisode.Release.Title; @@ -42,7 +42,25 @@ namespace NzbDrone.Core.Download.Clients.Blackhole using (var stream = _diskProvider.OpenWriteStream(filepath)) { - stream.Write(fileContent, 0, fileContent.Length); + stream.Write(fileContents, 0, fileContents.Length); + } + + _logger.Debug("NZB Download succeeded, saved to: {0}", filepath); + + return null; + } + + protected override string AddFromNzbFile(RemoteMovie remoteMovie, string filename, byte[] fileContents) + { + var title = remoteMovie.Release.Title; + + title = FileNameBuilder.CleanFileName(title); + + var filepath = Path.Combine(Settings.NzbFolder, title + ".nzb"); + + using (var stream = _diskProvider.OpenWriteStream(filepath)) + { + stream.Write(fileContents, 0, fileContents.Length); } _logger.Debug("NZB Download succeeded, saved to: {0}", filepath); diff --git a/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs b/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs index 39174d3b8..6312672a6 100644 --- a/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs +++ b/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs @@ -31,6 +31,50 @@ namespace NzbDrone.Core.Download.Clients.Deluge _proxy = proxy; } + protected override string AddFromMagnetLink(RemoteMovie remoteEpisode, string hash, string magnetLink) + { + var actualHash = _proxy.AddTorrentFromMagnet(magnetLink, Settings); + + if (!Settings.TvCategory.IsNullOrWhiteSpace()) + { + _proxy.SetLabel(actualHash, Settings.TvCategory, Settings); + } + + _proxy.SetTorrentConfiguration(actualHash, "remove_at_ratio", false, Settings); + + /*var isRecentEpisode = remoteEpisode.IsRecentEpisode(); + + if (isRecentEpisode && Settings.RecentTvPriority == (int)DelugePriority.First || + !isRecentEpisode && Settings.OlderTvPriority == (int)DelugePriority.First) + { + _proxy.MoveTorrentToTopInQueue(actualHash, Settings); + }*/ + + return actualHash.ToUpper(); + } + + protected override string AddFromTorrentFile(RemoteMovie remoteEpisode, string hash, string filename, byte[] fileContent) + { + var actualHash = _proxy.AddTorrentFromFile(filename, fileContent, Settings); + + if (!Settings.TvCategory.IsNullOrWhiteSpace()) + { + _proxy.SetLabel(actualHash, Settings.TvCategory, Settings); + } + + _proxy.SetTorrentConfiguration(actualHash, "remove_at_ratio", false, Settings); + + /*var isRecentEpisode = remoteEpisode.IsRecentEpisode(); + + if (isRecentEpisode && Settings.RecentTvPriority == (int)DelugePriority.First || + !isRecentEpisode && Settings.OlderTvPriority == (int)DelugePriority.First) + { + _proxy.MoveTorrentToTopInQueue(actualHash, Settings); + }*/ + + return actualHash.ToUpper(); + } + protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink) { var actualHash = _proxy.AddTorrentFromMagnet(magnetLink, Settings); diff --git a/src/NzbDrone.Core/Download/Clients/Deluge/DelugeSettings.cs b/src/NzbDrone.Core/Download/Clients/Deluge/DelugeSettings.cs index b5fd1153e..73bc0212a 100644 --- a/src/NzbDrone.Core/Download/Clients/Deluge/DelugeSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Deluge/DelugeSettings.cs @@ -25,7 +25,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge Host = "localhost"; Port = 8112; Password = "deluge"; - TvCategory = "tv-sonarr"; + TvCategory = "movie-radarr"; } [FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)] @@ -40,7 +40,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge [FieldDefinition(3, Label = "Password", Type = FieldType.Password)] public string Password { get; set; } - [FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional")] + [FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional")] public string TvCategory { get; set; } [FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] diff --git a/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs b/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs index 6d45f0386..ce29dafad 100644 --- a/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs +++ b/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs @@ -29,11 +29,25 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex _proxy = proxy; } - protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContent) + protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContents) { var priority = remoteEpisode.IsRecentEpisode() ? Settings.RecentTvPriority : Settings.OlderTvPriority; + + var response = _proxy.DownloadNzb(fileContents, filename, priority, Settings); - var response = _proxy.DownloadNzb(fileContent, filename, priority, Settings); + if (response == null) + { + throw new DownloadClientException("Failed to add nzb {0}", filename); + } + + return response; + } + + protected override string AddFromNzbFile(RemoteMovie remoteMovie, string filename, byte[] fileContents) + { + var priority = Settings.RecentTvPriority; + + var response = _proxy.DownloadNzb(fileContents, filename, priority, Settings); if (response == null) { diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs index b36c44c5f..c358040f2 100644 --- a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs +++ b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs @@ -29,12 +29,12 @@ namespace NzbDrone.Core.Download.Clients.Nzbget _proxy = proxy; } - protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContent) + protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContents) { var category = Settings.TvCategory; var priority = remoteEpisode.IsRecentEpisode() ? Settings.RecentTvPriority : Settings.OlderTvPriority; - var response = _proxy.DownloadNzb(fileContent, filename, category, priority, Settings); + var response = _proxy.DownloadNzb(fileContents, filename, category, priority, Settings); if (response == null) { @@ -46,7 +46,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget protected override string AddFromNzbFile(RemoteMovie remoteMovie, string filename, byte[] fileContents) { - var category = Settings.TvCategory; //could update this to MovieCategory + var category = Settings.TvCategory; // TODO: Update this to MovieCategory? var priority = Settings.RecentTvPriority; var response = _proxy.DownloadNzb(fileContents, filename, category, priority, Settings); diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs index fa6908f2c..d139d244b 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs @@ -22,7 +22,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent { Host = "localhost"; Port = 9091; - TvCategory = "tv-sonarr"; + TvCategory = "movie-radarr"; } [FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)] @@ -37,9 +37,10 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent [FieldDefinition(3, Label = "Password", Type = FieldType.Password)] public string Password { get; set; } - [FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional")] + [FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional")] public string TvCategory { get; set; } + //Todo: update this shit. [FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] public int RecentTvPriority { get; set; } diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs index 64a5e23de..bc32aedb1 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs @@ -32,12 +32,27 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd // patch can be a number (releases) or 'x' (git) private static readonly Regex VersionRegex = new Regex(@"(?\d+)\.(?\d+)\.(?\d+|x)(?.*)", RegexOptions.Compiled); - protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContent) + protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContents) { var category = Settings.TvCategory; var priority = remoteEpisode.IsRecentEpisode() ? Settings.RecentTvPriority : Settings.OlderTvPriority; + + var response = _proxy.DownloadNzb(fileContents, filename, category, priority, Settings); - var response = _proxy.DownloadNzb(fileContent, filename, category, priority, Settings); + if (response != null && response.Ids.Any()) + { + return response.Ids.First(); + } + + return null; + } + + protected override string AddFromNzbFile(RemoteMovie remoteMovie, string filename, byte[] fileContents) + { + var category = Settings.TvCategory; + var priority = Settings.RecentTvPriority; + + var response = _proxy.DownloadNzb(fileContents, filename, category, priority, Settings); if (response != null && response.Ids.Any()) { diff --git a/src/NzbDrone.Core/Download/DownloadService.cs b/src/NzbDrone.Core/Download/DownloadService.cs index 6d0963199..b7c4b205a 100644 --- a/src/NzbDrone.Core/Download/DownloadService.cs +++ b/src/NzbDrone.Core/Download/DownloadService.cs @@ -1,6 +1,5 @@ using System; using NLog; -using NzbDrone.Common.EnsureThat; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Common.Instrumentation.Extensions; diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs index c7ba36cbc..01f87f07a 100644 --- a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs +++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs @@ -57,12 +57,17 @@ namespace NzbDrone.Core.Download.TrackedDownloads try { var parsedEpisodeInfo = Parser.Parser.ParseTitle(trackedDownload.DownloadItem.Title); + var parsedMovieInfo = Parser.Parser.ParseMovieTitle(trackedDownload.DownloadItem.Title); var historyItems = _historyService.FindByDownloadId(downloadItem.DownloadId); + if (parsedMovieInfo != null) + { + trackedDownload.RemoteMovie = _parsingService.Map(parsedMovieInfo, "", null); + } + if (parsedEpisodeInfo != null) { trackedDownload.RemoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0, 0); - trackedDownload.RemoteMovie = _parsingService.Map(parsedEpisodeInfo, "", null); } if (historyItems.Any()) diff --git a/src/NzbDrone.Core/Download/UsenetClientBase.cs b/src/NzbDrone.Core/Download/UsenetClientBase.cs index e45bb2d89..0f2ea47de 100644 --- a/src/NzbDrone.Core/Download/UsenetClientBase.cs +++ b/src/NzbDrone.Core/Download/UsenetClientBase.cs @@ -30,12 +30,9 @@ namespace NzbDrone.Core.Download public override DownloadProtocol Protocol => DownloadProtocol.Usenet; - protected abstract string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContent); + protected abstract string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContents); - protected virtual string AddFromNzbFile(RemoteMovie remoteMovie, string filename, byte[] fileContents) - { - throw new NotImplementedException(); - } + protected abstract string AddFromNzbFile(RemoteMovie remoteMovie, string filename, byte[] fileContents); public override string Download(RemoteEpisode remoteEpisode) { diff --git a/src/NzbDrone.Core/History/HistoryService.cs b/src/NzbDrone.Core/History/HistoryService.cs index 8185a1c6e..634cf8b00 100644 --- a/src/NzbDrone.Core/History/HistoryService.cs +++ b/src/NzbDrone.Core/History/HistoryService.cs @@ -31,6 +31,7 @@ namespace NzbDrone.Core.History public class HistoryService : IHistoryService, IHandle, IHandle, + IHandle, IHandle, IHandle, IHandle, @@ -186,7 +187,7 @@ namespace NzbDrone.Core.History { EventType = HistoryEventType.Grabbed, Date = DateTime.UtcNow, - Quality = message.Movie.ParsedEpisodeInfo.Quality, + Quality = message.Movie.ParsedMovieInfo.Quality, SourceTitle = message.Movie.Release.Title, SeriesId = 0, EpisodeId = 0, @@ -196,7 +197,7 @@ namespace NzbDrone.Core.History history.Data.Add("Indexer", message.Movie.Release.Indexer); history.Data.Add("NzbInfoUrl", message.Movie.Release.InfoUrl); - history.Data.Add("ReleaseGroup", message.Movie.ParsedEpisodeInfo.ReleaseGroup); + history.Data.Add("ReleaseGroup", message.Movie.ParsedMovieInfo.ReleaseGroup); history.Data.Add("Age", message.Movie.Release.Age.ToString()); history.Data.Add("AgeHours", message.Movie.Release.AgeHours.ToString()); history.Data.Add("AgeMinutes", message.Movie.Release.AgeMinutes.ToString()); @@ -209,9 +210,9 @@ namespace NzbDrone.Core.History history.Data.Add("TvRageId", message.Movie.Release.TvRageId.ToString()); history.Data.Add("Protocol", ((int)message.Movie.Release.DownloadProtocol).ToString()); - if (!message.Movie.ParsedEpisodeInfo.ReleaseHash.IsNullOrWhiteSpace()) + if (!message.Movie.ParsedMovieInfo.ReleaseHash.IsNullOrWhiteSpace()) { - history.Data.Add("ReleaseHash", message.Movie.ParsedEpisodeInfo.ReleaseHash); + history.Data.Add("ReleaseHash", message.Movie.ParsedMovieInfo.ReleaseHash); } var torrentRelease = message.Movie.Release as TorrentInfo; @@ -264,6 +265,45 @@ namespace NzbDrone.Core.History } } + public void Handle(MovieImportedEvent message) + { + if (!message.NewDownload) + { + return; + } + + var downloadId = message.DownloadId; + + if (downloadId.IsNullOrWhiteSpace()) + { + //downloadId = FindDownloadId(message); For now fuck off. + } + + var movie = message.MovieInfo.Movie; + var history = new History + { + EventType = HistoryEventType.DownloadFolderImported, + Date = DateTime.UtcNow, + Quality = message.MovieInfo.Quality, + SourceTitle = movie.Title, + SeriesId = 0, + EpisodeId = 0, + DownloadId = downloadId, + MovieId = movie.Id, + + + }; + + //Won't have a value since we publish this event before saving to DB. + //history.Data.Add("FileId", message.ImportedEpisode.Id.ToString()); + history.Data.Add("DroppedPath", message.MovieInfo.Path); + history.Data.Add("ImportedPath", Path.Combine(movie.Path, message.ImportedMovie.RelativePath)); + history.Data.Add("DownloadClient", message.DownloadClient); + + _historyRepository.Insert(history); + + } + public void Handle(DownloadFailedEvent message) { foreach (var episodeId in message.EpisodeIds) diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs index 9cb004f67..c7343513c 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs @@ -30,6 +30,7 @@ namespace NzbDrone.Core.Indexers.Newznab public NewznabCapabilities GetCapabilities(NewznabSettings indexerSettings) { var key = indexerSettings.ToJson(); + _capabilitiesCache.Clear(); var capabilities = _capabilitiesCache.Get(key, () => FetchCapabilities(indexerSettings), TimeSpan.FromDays(7)); return capabilities; @@ -98,6 +99,16 @@ namespace NzbDrone.Core.Indexers.Newznab capabilities.SupportedTvSearchParameters = xmlTvSearch.Attribute("supportedParams").Value.Split(','); capabilities.SupportsAggregateIdSearch = true; } + var xmlMovieSearch = xmlSearching.Element("movie-search"); + if (xmlMovieSearch == null || xmlMovieSearch.Attribute("available").Value != "yes") + { + capabilities.SupportedMovieSearchParamters = null; + } + else if (xmlMovieSearch.Attribute("supportedParams") != null) + { + capabilities.SupportedMovieSearchParamters = xmlMovieSearch.Attribute("supportedParams").Value.Split(','); + capabilities.SupportsAggregateIdSearch = true; + } } var xmlCategories = xmlRoot.Element("categories"); diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs index 76030a2ec..518494923 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs @@ -92,9 +92,7 @@ namespace NzbDrone.Core.Indexers.Newznab var capabilities = _capabilitiesProvider.GetCapabilities(Settings); return capabilities.SupportedMovieSearchParamters != null && - capabilities.SupportedMovieSearchParamters.Contains("imdb") && - capabilities.SupportedMovieSearchParamters.Contains("imdbtitle") && - capabilities.SupportedMovieSearchParamters.Contains("imdbyear"); + capabilities.SupportedMovieSearchParamters.Contains("imdb"); } } @@ -131,6 +129,12 @@ namespace NzbDrone.Core.Indexers.Newznab pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "movie", string.Format("&imdbid={0}", searchCriteria.Movie.ImdbId.Substring(2)))); //strip off the "tt" - VERY HACKY } + else + { + //Let's try anyways with q parameter, worst case nothing found. + pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "search", + string.Format("&q={0}", searchCriteria.Movie.Title))); + } return pageableRequests; } diff --git a/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotato.cs b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotato.cs new file mode 100644 index 000000000..2eeb09f37 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotato.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Exceptions; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Http.CloudFlare; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.Indexers.TorrentPotato +{ + public class TorrentPotato : HttpIndexerBase + { + public override string Name => "TorrentPotato"; + + public override DownloadProtocol Protocol => DownloadProtocol.Torrent; + public override TimeSpan RateLimit => TimeSpan.FromSeconds(2); + + public TorrentPotato(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) + : base(httpClient, indexerStatusService, configService, parsingService, logger) + { + + } + + public override IEnumerable DefaultDefinitions + { + get + { + yield return GetDefinition("Jackett", new TorrentPotatoSettings { BaseUrl = "http://localhost:9117/potato/YOURINDEXER"}); + } + } + + private IndexerDefinition GetDefinition(string name, TorrentPotatoSettings settings) + { + return new IndexerDefinition + { + EnableRss = false, + EnableSearch = false, + Name = name, + Implementation = GetType().Name, + Settings = settings, + Protocol = DownloadProtocol.Torrent, + SupportsRss = SupportsRss, + SupportsSearch = SupportsSearch + }; + } + + public override IIndexerRequestGenerator GetRequestGenerator() + { + return new TorrentPotatoRequestGenerator() { Settings = Settings }; + } + + public override IParseIndexerResponse GetParser() + { + return new TorrentPotatoParser(); + } + + + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoParser.cs b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoParser.cs new file mode 100644 index 000000000..2c21e408e --- /dev/null +++ b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoParser.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.Net; +using System.Text.RegularExpressions; +using NzbDrone.Common.Http; +using NzbDrone.Core.Indexers.Exceptions; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.Indexers.TorrentPotato +{ + public class TorrentPotatoParser : IParseIndexerResponse + { + private static readonly Regex RegexGuid = new Regex(@"^magnet:\?xt=urn:btih:([a-f0-9]+)", RegexOptions.Compiled); + + public IList ParseResponse(IndexerResponse indexerResponse) + { + var results = new List(); + + switch (indexerResponse.HttpResponse.StatusCode) + { + default: + if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK) + { + throw new IndexerException(indexerResponse, "Indexer API call returned an unexpected StatusCode [{0}]", indexerResponse.HttpResponse.StatusCode); + } + break; + } + + var jsonResponse = new HttpResponse(indexerResponse.HttpResponse); + + foreach (var torrent in jsonResponse.Resource.results) + { + var torrentInfo = new TorrentInfo(); + + torrentInfo.Guid = GetGuid(torrent); + torrentInfo.Title = torrent.release_name; + torrentInfo.Size = (long)torrent.size*1000*1000; + torrentInfo.DownloadUrl = torrent.download_url; + torrentInfo.InfoUrl = torrent.details_url; + torrentInfo.PublishDate = new System.DateTime(); + torrentInfo.Seeders = torrent.seeders; + torrentInfo.Peers = torrent.leechers + torrent.seeders; + torrentInfo.Freeleech = torrent.freeleech; + + results.Add(torrentInfo); + } + + return results; + } + + private string GetGuid(Result torrent) + { + var match = RegexGuid.Match(torrent.download_url); + + if (match.Success) + { + return string.Format("potato-{0}", match.Groups[1].Value); + } + else + { + return string.Format("potato-{0}", torrent.download_url); + } + } + + } +} diff --git a/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoRequestGenerator.cs b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoRequestGenerator.cs new file mode 100644 index 000000000..5509c0754 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoRequestGenerator.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; +using NzbDrone.Core.IndexerSearch.Definitions; + +namespace NzbDrone.Core.Indexers.TorrentPotato +{ + public class TorrentPotatoRequestGenerator : IIndexerRequestGenerator + { + + public TorrentPotatoSettings Settings { get; set; } + + public TorrentPotatoRequestGenerator() + { + + } + + public virtual IndexerPageableRequestChain GetRecentRequests() + { + var pageableRequests = new IndexerPageableRequestChain(); + + pageableRequests.Add(GetPagedRequests("list", null, null)); + + return pageableRequests; + } + + public virtual IndexerPageableRequestChain GetSearchRequests(SingleEpisodeSearchCriteria searchCriteria) + { + var pageableRequests = new IndexerPageableRequestChain(); + + pageableRequests.Add(GetPagedRequests("search", searchCriteria.Series.TvdbId, "S{0:00}E{1:00}", searchCriteria.SeasonNumber, searchCriteria.EpisodeNumber)); + + return pageableRequests; + } + + public virtual IndexerPageableRequestChain GetSearchRequests(SeasonSearchCriteria searchCriteria) + { + var pageableRequests = new IndexerPageableRequestChain(); + + pageableRequests.Add(GetPagedRequests("search", searchCriteria.Series.TvdbId, "S{0:00}", searchCriteria.SeasonNumber)); + + return pageableRequests; + } + + public virtual IndexerPageableRequestChain GetSearchRequests(DailyEpisodeSearchCriteria searchCriteria) + { + var pageableRequests = new IndexerPageableRequestChain(); + + pageableRequests.Add(GetPagedRequests("search", searchCriteria.Series.TvdbId, "\"{0:yyyy MM dd}\"", searchCriteria.AirDate)); + + return pageableRequests; + } + + public virtual IndexerPageableRequestChain GetSearchRequests(AnimeEpisodeSearchCriteria searchCriteria) + { + return new IndexerPageableRequestChain(); + } + + public virtual IndexerPageableRequestChain GetSearchRequests(SpecialEpisodeSearchCriteria searchCriteria) + { + var pageableRequests = new IndexerPageableRequestChain(); + + foreach (var queryTitle in searchCriteria.EpisodeQueryTitles) + { + var query = queryTitle.Replace('+', ' '); + query = System.Web.HttpUtility.UrlEncode(query); + + pageableRequests.Add(GetPagedRequests("search", searchCriteria.Series.TvdbId, query)); + } + + return pageableRequests; + } + + private IEnumerable GetPagedRequests(string mode, int? tvdbId, string query, params object[] args) + { + var requestBuilder = new HttpRequestBuilder(Settings.BaseUrl) + .Accept(HttpAccept.Json); + + requestBuilder.AddQueryParam("passkey", Settings.Passkey); + requestBuilder.AddQueryParam("user", Settings.User); + requestBuilder.AddQueryParam("imdbid", "tt0076759"); //For now just search for Star Wars. + + + + yield return new IndexerRequest(requestBuilder.Build()); + } + + private IEnumerable GetMovieRequest(MovieSearchCriteria searchCriteria) + { + var requestBuilder = new HttpRequestBuilder(Settings.BaseUrl) + .Accept(HttpAccept.Json); + + requestBuilder.AddQueryParam("passkey", Settings.Passkey); + requestBuilder.AddQueryParam("user", Settings.User); + + if (searchCriteria.Movie.ImdbId.IsNotNullOrWhiteSpace()) + { + requestBuilder.AddQueryParam("imdbid", searchCriteria.Movie.ImdbId); + } + else + { + requestBuilder.AddQueryParam("search", searchCriteria.Movie.Title); + } + + yield return new IndexerRequest(requestBuilder.Build()); + } + + public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria) + { + + + var pageableRequests = new IndexerPageableRequestChain(); + + pageableRequests.Add(GetMovieRequest(searchCriteria)); + + return pageableRequests; + + + } + } +} diff --git a/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoResponse.cs b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoResponse.cs new file mode 100644 index 000000000..9b8300405 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoResponse.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; + +namespace NzbDrone.Core.Indexers.TorrentPotato +{ + + public class TorrentPotatoResponse + { + public Result[] results { get; set; } + public int total_results { get; set; } + } + + public class Result + { + public string release_name { get; set; } + public string torrent_id { get; set; } + public string details_url { get; set; } + public string download_url { get; set; } + public bool freeleech { get; set; } + public string type { get; set; } + public int size { get; set; } + public int leechers { get; set; } + public int seeders { get; set; } + } + +} diff --git a/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoSettings.cs b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoSettings.cs new file mode 100644 index 000000000..d0b902f5e --- /dev/null +++ b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoSettings.cs @@ -0,0 +1,38 @@ +using FluentValidation; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.Indexers.TorrentPotato +{ + public class TorrentPotatoSettingsValidator : AbstractValidator + { + public TorrentPotatoSettingsValidator() + { + RuleFor(c => c.BaseUrl).ValidRootUrl(); + } + } + + public class TorrentPotatoSettings : IProviderConfig + { + private static readonly TorrentPotatoSettingsValidator Validator = new TorrentPotatoSettingsValidator(); + + public TorrentPotatoSettings() + { + BaseUrl = ""; + } + + [FieldDefinition(0, Label = "API URL", HelpText = "URL to TorrentPotato api.")] + public string BaseUrl { get; set; } + + [FieldDefinition(1, Label = "Username", HelpText = "The username you use at your indexer.")] + public string User { get; set; } + + [FieldDefinition(2, Label = "Passkey", HelpText = "The password you use at your Indexer,")] + public string Passkey { get; set; } + public NzbDroneValidationResult Validate() + { + return new NzbDroneValidationResult(Validator.Validate(this)); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Jobs/ScheduledTask.cs b/src/NzbDrone.Core/Jobs/ScheduledTask.cs index 5d842696d..a91faf3d1 100644 --- a/src/NzbDrone.Core/Jobs/ScheduledTask.cs +++ b/src/NzbDrone.Core/Jobs/ScheduledTask.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.Jobs public class ScheduledTask : ModelBase { public string TypeName { get; set; } - public int Interval { get; set; } + public double Interval { get; set; } public DateTime LastExecution { get; set; } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Jobs/TaskManager.cs b/src/NzbDrone.Core/Jobs/TaskManager.cs index 3ad7b909a..33ba087b4 100644 --- a/src/NzbDrone.Core/Jobs/TaskManager.cs +++ b/src/NzbDrone.Core/Jobs/TaskManager.cs @@ -61,7 +61,7 @@ namespace NzbDrone.Core.Jobs { var defaultTasks = new[] { - new ScheduledTask{ Interval = 1, TypeName = typeof(CheckForFinishedDownloadCommand).FullName}, + new ScheduledTask{ Interval = 0.25f, TypeName = typeof(CheckForFinishedDownloadCommand).FullName}, new ScheduledTask{ Interval = 5, TypeName = typeof(MessagingCleanupCommand).FullName}, new ScheduledTask{ Interval = 6*60, TypeName = typeof(ApplicationUpdateCommand).FullName}, new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName}, diff --git a/src/NzbDrone.Core/MetadataSource/IProvideMovieInfo.cs b/src/NzbDrone.Core/MetadataSource/IProvideMovieInfo.cs index 861564bc4..e5256a6dc 100644 --- a/src/NzbDrone.Core/MetadataSource/IProvideMovieInfo.cs +++ b/src/NzbDrone.Core/MetadataSource/IProvideMovieInfo.cs @@ -7,5 +7,6 @@ namespace NzbDrone.Core.MetadataSource public interface IProvideMovieInfo { Movie GetMovieInfo(string ImdbId); + Movie GetMovieInfo(int TmdbId); } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ConfigurationResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ConfigurationResource.cs new file mode 100644 index 000000000..808bd9ab9 --- /dev/null +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ConfigurationResource.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.MetadataSource.SkyHook.Resource +{ + + public class ConfigResource + { + public Images images { get; set; } + public string[] change_keys { get; set; } + } + + public class Images + { + public string base_url { get; set; } + public string secure_base_url { get; set; } + public string[] backdrop_sizes { get; set; } + public string[] logo_sizes { get; set; } + public string[] poster_sizes { get; set; } + public string[] profile_sizes { get; set; } + public string[] still_sizes { get; set; } + } + +} diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/MovieResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/MovieResource.cs new file mode 100644 index 000000000..72e3534e2 --- /dev/null +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/MovieResource.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace NzbDrone.Core.MetadataSource.SkyHook.Resource +{ + public class ImdbResource + { + public int v { get; set; } + public string q { get; set; } + public MovieResource[] d { get; set; } + } + + public class MovieResource + { + public string l { get; set; } + public string id { get; set; } + public string s { get; set; } + public int y { get; set; } + public string q { get; set; } + public object[] i { get; set; } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs new file mode 100644 index 000000000..d6076d3fd --- /dev/null +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.MetadataSource.SkyHook.Resource +{ + + public class MovieSearchRoot + { + public int page { get; set; } + public MovieResult[] results { get; set; } + public int total_results { get; set; } + public int total_pages { get; set; } + } + + public class MovieResult + { + public string poster_path { get; set; } + public bool adult { get; set; } + public string overview { get; set; } + public string release_date { get; set; } + public int?[] genre_ids { get; set; } + public int id { get; set; } + public string original_title { get; set; } + public string original_language { get; set; } + public string title { get; set; } + public string backdrop_path { get; set; } + public float popularity { get; set; } + public int vote_count { get; set; } + public bool video { get; set; } + public float vote_average { get; set; } + } + + + public class MovieResourceRoot + { + public bool adult { get; set; } + public string backdrop_path { get; set; } + public Belongs_To_Collection belongs_to_collection { get; set; } + public int budget { get; set; } + public Genre[] genres { get; set; } + public string homepage { get; set; } + public int id { get; set; } + public string imdb_id { get; set; } + public string original_language { get; set; } + public string original_title { get; set; } + public string overview { get; set; } + public float popularity { get; set; } + public string poster_path { get; set; } + public Production_Companies[] production_companies { get; set; } + public Production_Countries[] production_countries { get; set; } + public string release_date { get; set; } + public int revenue { get; set; } + public int runtime { get; set; } + public Spoken_Languages[] spoken_languages { get; set; } + public string status { get; set; } + public string tagline { get; set; } + public string title { get; set; } + public bool video { get; set; } + public float vote_average { get; set; } + public int vote_count { get; set; } + public AlternativeTitles alternative_titles { get; set; } + } + + public class Belongs_To_Collection + { + public int id { get; set; } + public string name { get; set; } + public string poster_path { get; set; } + public string backdrop_path { get; set; } + } + + public class Genre + { + public int id { get; set; } + public string name { get; set; } + } + + public class Production_Companies + { + public string name { get; set; } + public int id { get; set; } + } + + public class Production_Countries + { + public string iso_3166_1 { get; set; } + public string name { get; set; } + } + + public class Spoken_Languages + { + public string iso_639_1 { get; set; } + public string name { get; set; } + } + + public class AlternativeTitles + { + public List titles { get; set; } + } + + public class Title + { + public string iso_3166_1 { get; set; } + public string title { get; set; } + } +} diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index 8b996fedd..e0014a728 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -9,6 +9,7 @@ using NzbDrone.Common.Http; using NzbDrone.Core.Exceptions; using NzbDrone.Core.MediaCover; using NzbDrone.Core.MetadataSource.SkyHook.Resource; +using NzbDrone.Core.MetadataSource; using NzbDrone.Core.Tv; using Newtonsoft.Json; @@ -20,11 +21,15 @@ namespace NzbDrone.Core.MetadataSource.SkyHook private readonly Logger _logger; private readonly IHttpRequestBuilderFactory _requestBuilder; + private readonly IHttpRequestBuilderFactory _movieBuilder; + private readonly ITmdbConfigService _configService; - public SkyHookProxy(IHttpClient httpClient, ISonarrCloudRequestBuilder requestBuilder, Logger logger) + public SkyHookProxy(IHttpClient httpClient, ISonarrCloudRequestBuilder requestBuilder, ITmdbConfigService configService, Logger logger) { _httpClient = httpClient; _requestBuilder = requestBuilder.SkyHookTvdb; + _movieBuilder = requestBuilder.TMDB; + _configService = configService; _logger = logger; } @@ -58,6 +63,65 @@ namespace NzbDrone.Core.MetadataSource.SkyHook return new Tuple<Series, List<Episode>>(series, episodes.ToList()); } + public Movie GetMovieInfo(int TmdbId) + { + var request = _movieBuilder.Create() + .SetSegment("route", "movie") + .SetSegment("id", TmdbId.ToString()) + .SetSegment("secondaryRoute", "") + .AddQueryParam("append_to_response", "alternative_titles") + .AddQueryParam("country", "US") + .Build(); + + request.AllowAutoRedirect = true; + request.SuppressHttpError = true; + + var response = _httpClient.Get<MovieResourceRoot>(request); + + var resource = response.Resource; + + var movie = new Movie(); + + movie.TmdbId = TmdbId; + movie.ImdbId = resource.imdb_id; + movie.Title = resource.title; + movie.TitleSlug = movie.Title.ToLower().Replace(" ", "-"); + movie.CleanTitle = Parser.Parser.CleanSeriesTitle(movie.Title); + movie.Overview = resource.overview; + movie.Website = resource.homepage; + movie.InCinemas = DateTime.Parse(resource.release_date); + movie.Year = movie.InCinemas.Value.Year; + + movie.Images.Add(_configService.GetCoverForURL(resource.poster_path, MediaCoverTypes.Poster));//TODO: Update to load image specs from tmdb page! + movie.Images.Add(_configService.GetCoverForURL(resource.backdrop_path, MediaCoverTypes.Banner)); + movie.Runtime = resource.runtime; + + foreach(Title title in resource.alternative_titles.titles) + { + movie.AlternativeTitles.Add(title.title); + } + + movie.Ratings = new Ratings(); + movie.Ratings.Votes = resource.vote_count; + movie.Ratings.Value = (decimal)resource.vote_average; + + foreach(Genre genre in resource.genres) + { + movie.Genres.Add(genre.name); + } + + if (resource.status == "Released") + { + movie.Status = MovieStatusType.Released; + } + else + { + movie.Status = MovieStatusType.Announced; + } + + return movie; + } + public Movie GetMovieInfo(string ImdbId) { var imdbRequest = new HttpRequest("http://www.omdbapi.com/?i=" + ImdbId + "&plot=full&r=json"); @@ -136,11 +200,22 @@ namespace NzbDrone.Core.MetadataSource.SkyHook } } - var searchTerm = lowerTitle.Replace("+", "_").Replace(" ", "_"); + var searchTerm = lowerTitle.Replace("_", "+").Replace(" ", "+"); var firstChar = searchTerm.First(); - var imdbRequest = new HttpRequest("https://v2.sg.media-imdb.com/suggests/" + firstChar + "/" + searchTerm + ".json"); + var request = _movieBuilder.Create() + .SetSegment("route", "search") + .SetSegment("id", "movie") + .SetSegment("secondaryRoute", "") + .AddQueryParam("query", searchTerm) + .AddQueryParam("include_adult", false) + .Build(); + + request.AllowAutoRedirect = true; + request.SuppressHttpError = true; + + /*var imdbRequest = new HttpRequest("https://v2.sg.media-imdb.com/suggests/" + firstChar + "/" + searchTerm + ".json"); var response = _httpClient.Get(imdbRequest); @@ -148,31 +223,42 @@ namespace NzbDrone.Core.MetadataSource.SkyHook var responseCleaned = response.Content.Replace(imdbCallback, "").TrimEnd(")"); - dynamic json = JsonConvert.DeserializeObject(responseCleaned); + _logger.Warn("Cleaned response: " + responseCleaned); + + ImdbResource json = JsonConvert.DeserializeObject<ImdbResource>(responseCleaned); + + _logger.Warn("Json object: " + json); + + _logger.Warn("Crash ahead.");*/ + + var response = _httpClient.Get<MovieSearchRoot>(request); + + var movieResults = response.Resource.results; var imdbMovies = new List<Movie>(); - foreach (dynamic entry in json.d) + foreach (MovieResult result in movieResults) { var imdbMovie = new Movie(); - imdbMovie.ImdbId = entry.id; + imdbMovie.TmdbId = result.id; try { - imdbMovie.SortTitle = entry.l; - imdbMovie.Title = entry.l; - string titleSlug = entry.l; + imdbMovie.SortTitle = result.title; + imdbMovie.Title = result.title; + string titleSlug = result.title; imdbMovie.TitleSlug = titleSlug.ToLower().Replace(" ", "-"); - imdbMovie.Year = entry.y; + imdbMovie.Year = DateTime.Parse(result.release_date).Year; imdbMovie.Images = new List<MediaCover.MediaCover>(); + imdbMovie.Overview = result.overview; try { - string url = entry.i[0]; - var imdbPoster = new MediaCover.MediaCover(MediaCoverTypes.Poster, url); + string url = result.poster_path; + var imdbPoster = _configService.GetCoverForURL(result.poster_path, MediaCoverTypes.Poster); imdbMovie.Images.Add(imdbPoster); } catch (Exception e) { - _logger.Debug(entry); + _logger.Debug(result); continue; } diff --git a/src/NzbDrone.Core/MetadataSource/TmdbConfigurationService.cs b/src/NzbDrone.Core/MetadataSource/TmdbConfigurationService.cs new file mode 100644 index 000000000..2b960f0de --- /dev/null +++ b/src/NzbDrone.Core/MetadataSource/TmdbConfigurationService.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NzbDrone.Core.MediaCover; +using NzbDrone.Common.Cache; +using NzbDrone.Common.Http; +using NzbDrone.Common.Cloud; +using NzbDrone.Core.MetadataSource.SkyHook.Resource; + +namespace NzbDrone.Core.MetadataSource +{ + public interface ITmdbConfigService + { + MediaCover.MediaCover GetCoverForURL(string url, MediaCover.MediaCoverTypes type); + } + + class TmdbConfigService : ITmdbConfigService + { + private readonly ICached<ConfigResource> _configurationCache; + private readonly IHttpClient _httpClient; + private readonly IHttpRequestBuilderFactory _tmdbBuilder; + + public TmdbConfigService(ICacheManager cacheManager, IHttpClient httpClient, ISonarrCloudRequestBuilder requestBuilder) + { + _configurationCache = cacheManager.GetCache<ConfigResource>(GetType(), "configuration_cache"); + _httpClient = httpClient; + _tmdbBuilder = requestBuilder.TMDBSingle; + } + + public MediaCover.MediaCover GetCoverForURL(string url, MediaCover.MediaCoverTypes type) + { + if (_configurationCache.Count == 0) + { + RefreshCache(); + } + + var images = _configurationCache.Find("configuration").images; + + var cover = new MediaCover.MediaCover(); + cover.CoverType = type; + + var realUrl = images.base_url; + + switch (type) + { + case MediaCoverTypes.Banner: + realUrl += images.backdrop_sizes.Last(); + break; + case MediaCoverTypes.Poster: + realUrl += images.poster_sizes.Last(); + break; + default: + realUrl += "original"; + break; + } + + realUrl += url; + + cover.Url = realUrl; + + return cover; + } + + private void RefreshCache() + { + var request = _tmdbBuilder.Create().SetSegment("route", "configuration").Build(); + + var response = _httpClient.Get<ConfigResource>(request); + + if (response.Resource.images != null) + { + _configurationCache.Set("configuration", response.Resource); + } + } + } +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index cb3dfcba2..14d74a9e5 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -183,6 +183,10 @@ <Compile Include="Datastore\Migration\002_remove_tvrage_imdb_unique_constraint.cs" /> <Compile Include="Datastore\Migration\003_remove_clean_title_from_scene_mapping.cs" /> <Compile Include="Datastore\Migration\004_updated_history.cs" /> + <Compile Include="Datastore\Migration\108_update_schedule_interval.cs" /> + <Compile Include="Datastore\Migration\107_fix_movie_files.cs" /> + <Compile Include="Datastore\Migration\106_add_tmdb_stuff.cs" /> + <Compile Include="Datastore\Migration\105_fix_history_movieId.cs" /> <Compile Include="Datastore\Migration\005_added_eventtype_to_history.cs" /> <Compile Include="Datastore\Migration\006_add_index_to_log_time.cs" /> <Compile Include="Datastore\Migration\007_add_renameEpisodes_to_naming.cs" /> @@ -630,6 +634,11 @@ <Compile Include="Indexers\Omgwtfnzbs\OmgwtfnzbsRssParser.cs" /> <Compile Include="Indexers\Omgwtfnzbs\OmgwtfnzbsSettings.cs" /> <Compile Include="Indexers\HttpIndexerBase.cs" /> + <Compile Include="Indexers\TorrentPotato\TorrentPotato.cs" /> + <Compile Include="Indexers\TorrentPotato\TorrentPotatoParser.cs" /> + <Compile Include="Indexers\TorrentPotato\TorrentPotatoRequestGenerator.cs" /> + <Compile Include="Indexers\TorrentPotato\TorrentPotatoResponse.cs" /> + <Compile Include="Indexers\TorrentPotato\TorrentPotatoSettings.cs" /> <Compile Include="Indexers\Rarbg\Rarbg.cs" /> <Compile Include="Indexers\Rarbg\RarbgRequestGenerator.cs" /> <Compile Include="Indexers\Rarbg\RarbgResponse.cs" /> @@ -803,12 +812,15 @@ <Compile Include="MetadataSource\IProvideMovieInfo.cs" /> <Compile Include="MetadataSource\ISearchForNewMovie.cs" /> <Compile Include="MetadataSource\SkyHook\Resource\ActorResource.cs" /> + <Compile Include="MetadataSource\SkyHook\Resource\ConfigurationResource.cs" /> <Compile Include="MetadataSource\SkyHook\Resource\EpisodeResource.cs" /> <Compile Include="MetadataSource\SkyHook\Resource\ImageResource.cs" /> <Compile Include="MetadataSource\SkyHook\Resource\RatingResource.cs" /> <Compile Include="MetadataSource\SkyHook\Resource\SeasonResource.cs" /> + <Compile Include="MetadataSource\SkyHook\Resource\MovieResource.cs" /> <Compile Include="MetadataSource\SkyHook\Resource\ShowResource.cs" /> <Compile Include="MetadataSource\SkyHook\Resource\TimeOfDayResource.cs" /> + <Compile Include="MetadataSource\SkyHook\Resource\TMDBResources.cs" /> <Compile Include="MetadataSource\SkyHook\SkyHookProxy.cs" /> <Compile Include="MetadataSource\SearchSeriesComparer.cs" /> <Compile Include="MetadataSource\SkyHook\SkyHookException.cs" /> @@ -833,6 +845,7 @@ <Compile Include="Extras\Metadata\MetadataType.cs" /> <Compile Include="MetadataSource\IProvideSeriesInfo.cs" /> <Compile Include="MetadataSource\ISearchForNewSeries.cs" /> + <Compile Include="MetadataSource\TmdbConfigurationService.cs" /> <Compile Include="Notifications\Join\JoinAuthException.cs" /> <Compile Include="Notifications\Join\JoinInvalidDeviceException.cs" /> <Compile Include="Notifications\Join\JoinResponseModel.cs" /> @@ -889,6 +902,7 @@ <Compile Include="Parser\IsoLanguages.cs" /> <Compile Include="Parser\LanguageParser.cs" /> <Compile Include="Parser\Model\LocalMovie.cs" /> + <Compile Include="Parser\Model\ParsedMovieInfo.cs" /> <Compile Include="Parser\Model\RemoteMovie.cs" /> <Compile Include="Profiles\Delay\DelayProfile.cs" /> <Compile Include="Profiles\Delay\DelayProfileService.cs" /> diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index bac486ec1..17d1f4c57 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -346,7 +346,9 @@ namespace NzbDrone.Core.Organizer public static string CleanFolderName(string name) { name = FileNameCleanupRegex.Replace(name, match => match.Captures[0].Value[0].ToString()); - return name.Trim(' ', '.'); + name = name.Trim(' ', '.'); + + return CleanFileName(name); } private void AddSeriesTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Series series) diff --git a/src/NzbDrone.Core/Parser/Model/ParsedMovieInfo.cs b/src/NzbDrone.Core/Parser/Model/ParsedMovieInfo.cs new file mode 100644 index 000000000..ff1f87cfd --- /dev/null +++ b/src/NzbDrone.Core/Parser/Model/ParsedMovieInfo.cs @@ -0,0 +1,31 @@ +using System.Linq; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Qualities; + +namespace NzbDrone.Core.Parser.Model +{ + public class ParsedMovieInfo + { + public string MovieTitle { get; set; } + public SeriesTitleInfo MovieTitleInfo { get; set; } + public QualityModel Quality { get; set; } + public int SeasonNumber { get; set; } + public Language Language { get; set; } + public bool FullSeason { get; set; } + public bool Special { get; set; } + public string ReleaseGroup { get; set; } + public string ReleaseHash { get; set; } + public string Edition { get; set;} + public int Year { get; set; } + + public ParsedMovieInfo() + { + + } + + public override string ToString() + { + return string.Format("{0} - {1} {2}", MovieTitle, MovieTitleInfo.Year, Quality); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Parser/Model/RemoteMovie.cs b/src/NzbDrone.Core/Parser/Model/RemoteMovie.cs index d8ba07803..1e6f5f5cc 100644 --- a/src/NzbDrone.Core/Parser/Model/RemoteMovie.cs +++ b/src/NzbDrone.Core/Parser/Model/RemoteMovie.cs @@ -9,6 +9,7 @@ namespace NzbDrone.Core.Parser.Model { public ReleaseInfo Release { get; set; } public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; } //TODO: Change to ParsedMovieInfo, for now though ParsedEpisodeInfo will do. + public ParsedMovieInfo ParsedMovieInfo { get; set; } public Movie Movie { get; set; } public bool DownloadAllowed { get; set; } diff --git a/src/NzbDrone.Core/Parser/Model/TorrentInfo.cs b/src/NzbDrone.Core/Parser/Model/TorrentInfo.cs index 59aab44a0..dbbf7f1e9 100644 --- a/src/NzbDrone.Core/Parser/Model/TorrentInfo.cs +++ b/src/NzbDrone.Core/Parser/Model/TorrentInfo.cs @@ -8,6 +8,7 @@ namespace NzbDrone.Core.Parser.Model public string InfoHash { get; set; } public int? Seeders { get; set; } public int? Peers { get; set; } + public bool Freeleech { get; set; } public static int? GetSeeders(ReleaseInfo release) { diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index f482c2c9a..a48204a28 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -15,6 +15,29 @@ namespace NzbDrone.Core.Parser { private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(Parser)); + private static readonly Regex[] ReportMovieTitleRegex = new[] + { + //Special, Despecialized, etc. Edition Movies, e.g: Mission.Impossible.3.Special.Edition.2011 + new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![()\[!]))*(?<edition>(\w+\.?edition))\.(?<year>(?<!e|x)\d{4}(?!p|i|\d+|\)|\]|\W\d+)))+(\W+|_|$)(?!\\)", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + //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>(?<!e|x)\d{4}(?!p|i|\d+|\)|\]|\W\d+)))+(\W+|_|$)(?!\\)(?<edition>((\w+\.?){1,3}edition))", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + //Cut Movies, e.g: Mission.Impossible.3.Directors.Cut.2011 + new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![()\[!]))*(?<edition>(\w+\.?cut))\.(?<year>(?<!e|x)\d{4}(?!p|i|\d+|\)|\]|\W\d+)))+(\W+|_|$)(?!\\)", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + //Cut Movies, e.g: Mission.Impossible.3.2011.Directors.Cut + new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![()\[!]))*(?<year>(?<!e|x)\d{4}(?!p|i|\d+|\)|\]|\W\d+)))+(\W+|_|$)(?!\\)(?<edition>((\w+\.?){1,3}cut))", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + + //Normal movie format, e.g: Mission.Impossible.3.2011 + new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![()\[!]))*(?<year>(?<!e|x)\d{4}(?!p|i|\d+|\)|\]|\W\d+)))+(\W+|_|$)(?!\\)", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + //PassThePopcorn Torrent names: Star.Wars[PassThePopcorn] + new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![()\[!]))*(?<year>(\[\w *\])))+(\W+|_|$)(?!\\)", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + }; + private static readonly Regex[] ReportTitleRegex = new[] { //Anime - Absolute Episode Number + Title + Season+Episode @@ -298,6 +321,96 @@ namespace NzbDrone.Core.Parser return result; } + public static ParsedMovieInfo ParseMovieTitle(string title) + { + + ParsedMovieInfo realResult = null; + try + { + if (!ValidateBeforeParsing(title)) return null; + + title = title.Replace(" ", "."); //TODO: Determine if this breaks something. However, it shouldn't. + + Logger.Debug("Parsing string '{0}'", title); + + if (ReversedTitleRegex.IsMatch(title)) + { + var titleWithoutExtension = RemoveFileExtension(title).ToCharArray(); + Array.Reverse(titleWithoutExtension); + + title = new string(titleWithoutExtension) + title.Substring(titleWithoutExtension.Length); + + Logger.Debug("Reversed name detected. Converted to '{0}'", title); + } + + var simpleTitle = SimpleTitleRegex.Replace(title, string.Empty); + + simpleTitle = RemoveFileExtension(simpleTitle); + + // TODO: Quick fix stripping [url] - prefixes. + simpleTitle = WebsitePrefixRegex.Replace(simpleTitle, string.Empty); + + simpleTitle = CleanTorrentSuffixRegex.Replace(simpleTitle, string.Empty); + + foreach (var regex in ReportMovieTitleRegex) + { + var match = regex.Matches(simpleTitle); + + if (match.Count != 0) + { + Logger.Trace(regex); + try + { + var result = ParseMovieMatchCollection(match); + + if (result != null) + { + + result.Language = LanguageParser.ParseLanguage(title); + Logger.Debug("Language parsed: {0}", result.Language); + + result.Quality = QualityParser.ParseQuality(title); + Logger.Debug("Quality parsed: {0}", result.Quality); + + result.ReleaseGroup = ParseReleaseGroup(title); + + var subGroup = GetSubGroup(match); + if (!subGroup.IsNullOrWhiteSpace()) + { + result.ReleaseGroup = subGroup; + } + + Logger.Debug("Release Group parsed: {0}", result.ReleaseGroup); + + result.ReleaseHash = GetReleaseHash(match); + if (!result.ReleaseHash.IsNullOrWhiteSpace()) + { + Logger.Debug("Release Hash parsed: {0}", result.ReleaseHash); + } + + realResult = result; + + return result; + } + } + catch (InvalidDateException ex) + { + Logger.Debug(ex, ex.Message); + break; + } + } + } + } + catch (Exception e) + { + if (!title.ToLower().Contains("password") && !title.ToLower().Contains("yenc")) + Logger.Error(e, "An error has occurred while trying to parse " + title); + } + + Logger.Debug("Unable to parse {0}", title); + return realResult; + } + public static ParsedEpisodeInfo ParseTitle(string title) { @@ -528,6 +641,31 @@ namespace NzbDrone.Core.Parser return seriesTitleInfo; } + private static ParsedMovieInfo ParseMovieMatchCollection(MatchCollection matchCollection) + { + var seriesName = matchCollection[0].Groups["title"].Value.Replace('.', ' ').Replace('_', ' '); + seriesName = RequestInfoRegex.Replace(seriesName, "").Trim(' '); + + int airYear; + int.TryParse(matchCollection[0].Groups["year"].Value, out airYear); + + ParsedMovieInfo result; + + result = new ParsedMovieInfo { Year = airYear }; + + if (matchCollection[0].Groups["edition"].Success) + { + result.Edition = matchCollection[0].Groups["edition"].Value.Replace(".", " "); + } + + result.MovieTitle = seriesName; + result.MovieTitleInfo = GetSeriesTitleInfo(result.MovieTitle); + + Logger.Debug("Movie Parsed. {0}", result); + + return result; + } + private static ParsedEpisodeInfo ParseMatchCollection(MatchCollection matchCollection) { var seriesName = matchCollection[0].Groups["title"].Value.Replace('.', ' ').Replace('_', ' '); diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs index 5466bcb5a..bb8c794d5 100644 --- a/src/NzbDrone.Core/Parser/ParsingService.cs +++ b/src/NzbDrone.Core/Parser/ParsingService.cs @@ -21,7 +21,7 @@ namespace NzbDrone.Core.Parser Movie GetMovie(string title); RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null); RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable<int> episodeIds); - RemoteMovie Map(ParsedEpisodeInfo parsedEpisodeInfo, string imdbId, SearchCriteriaBase searchCriteria = null); + RemoteMovie Map(ParsedMovieInfo parsedMovieInfo, string imdbId, SearchCriteriaBase searchCriteria = null); List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null); ParsedEpisodeInfo ParseSpecialEpisodeTitle(string title, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null); } @@ -33,6 +33,20 @@ namespace NzbDrone.Core.Parser private readonly ISceneMappingService _sceneMappingService; private readonly IMovieService _movieService; private readonly Logger _logger; + private readonly Dictionary<string, string> romanNumeralsMapper = new Dictionary<string, string> + { + { "1", "I"}, + { "2", "II"}, + { "3", "III"}, + { "4", "IV"}, + { "5", "V"}, + { "6", "VI"}, + { "7", "VII"}, + { "8", "VII"}, + { "9", "IX"}, + { "10", "X"}, + + }; //If a movie has more than 10 parts fuck 'em. public ParsingService(IEpisodeService episodeService, ISeriesService seriesService, @@ -163,19 +177,19 @@ namespace NzbDrone.Core.Parser public Movie GetMovie(string title) { - var parsedEpisodeInfo = Parser.ParseTitle(title); + var parsedEpisodeInfo = Parser.ParseMovieTitle(title); if (parsedEpisodeInfo == null) { return _movieService.FindByTitle(title); } - var series = _movieService.FindByTitle(parsedEpisodeInfo.SeriesTitle); + var series = _movieService.FindByTitle(parsedEpisodeInfo.MovieTitle); if (series == null) { - series = _movieService.FindByTitle(parsedEpisodeInfo.SeriesTitleInfo.TitleWithoutYear, - parsedEpisodeInfo.SeriesTitleInfo.Year); + series = _movieService.FindByTitle(parsedEpisodeInfo.MovieTitleInfo.TitleWithoutYear, + parsedEpisodeInfo.MovieTitleInfo.Year); } return series; @@ -201,11 +215,11 @@ namespace NzbDrone.Core.Parser return remoteEpisode; } - public RemoteMovie Map(ParsedEpisodeInfo parsedEpisodeInfo, string imdbId, SearchCriteriaBase searchCriteria = null) + public RemoteMovie Map(ParsedMovieInfo parsedEpisodeInfo, string imdbId, SearchCriteriaBase searchCriteria = null) { var remoteEpisode = new RemoteMovie { - ParsedEpisodeInfo = parsedEpisodeInfo, + ParsedMovieInfo = parsedEpisodeInfo, }; var movie = GetMovie(parsedEpisodeInfo, imdbId, searchCriteria); @@ -334,23 +348,56 @@ namespace NzbDrone.Core.Parser return null; } - private Movie GetMovie(ParsedEpisodeInfo parsedEpisodeInfo, string imdbId, SearchCriteriaBase searchCriteria) + private Movie GetMovie(ParsedMovieInfo parsedEpisodeInfo, string imdbId, SearchCriteriaBase searchCriteria) { if (searchCriteria != null) { - if (searchCriteria.Movie.CleanTitle == parsedEpisodeInfo.SeriesTitle.CleanSeriesTitle()) + var possibleTitles = new List<string>(); + + possibleTitles.Add(searchCriteria.Movie.CleanTitle); + + foreach (string altTitle in searchCriteria.Movie.AlternativeTitles) { - return searchCriteria.Movie; + possibleTitles.Add(altTitle.CleanSeriesTitle()); } - if (imdbId.IsNotNullOrWhiteSpace() && imdbId == searchCriteria.Movie.ImdbId) + foreach (string title in possibleTitles) { - //TODO: If series is found by TvdbId, we should report it as a scene naming exception, since it will fail to import - return searchCriteria.Movie; + if (title == parsedEpisodeInfo.MovieTitle.CleanSeriesTitle()) + { + return searchCriteria.Movie; + } + + foreach (KeyValuePair<string, string> entry in romanNumeralsMapper) + { + string num = entry.Key; + string roman = entry.Value.ToLower(); + + if (title.Replace(num, roman) == parsedEpisodeInfo.MovieTitle.CleanSeriesTitle()) + { + return searchCriteria.Movie; + } + + if (title.Replace(roman, num) == parsedEpisodeInfo.MovieTitle.CleanSeriesTitle()) + { + return searchCriteria.Movie; + } + } } + + } + + Movie movie = null; + + if (searchCriteria == null) + { + + movie = _movieService.FindByTitle(parsedEpisodeInfo.MovieTitle); //Todo: same as above! + + return movie; } - Movie movie = _movieService.FindByTitle(parsedEpisodeInfo.SeriesTitle); + if (movie == null && imdbId.IsNotNullOrWhiteSpace()) { @@ -360,7 +407,7 @@ namespace NzbDrone.Core.Parser if (movie == null) { - _logger.Debug("No matching movie {0}", parsedEpisodeInfo.SeriesTitle); + _logger.Debug("No matching movie {0}", parsedEpisodeInfo.MovieTitle); return null; } diff --git a/src/NzbDrone.Core/Parser/QualityParser.cs b/src/NzbDrone.Core/Parser/QualityParser.cs index 7154cd3fd..696b979fb 100644 --- a/src/NzbDrone.Core/Parser/QualityParser.cs +++ b/src/NzbDrone.Core/Parser/QualityParser.cs @@ -28,6 +28,8 @@ namespace NzbDrone.Core.Parser )\b", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + private static readonly Regex HardcodedSubsRegex = new Regex(@"\b(?<hcsub>(\w+SUB))|(?<hc>(HC))\b", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + private static readonly Regex RawHDRegex = new Regex(@"\b(?<rawhd>RawHD|1080i[-_. ]HDTV|Raw[-_. ]HD|MPEG[-_. ]?2)\b", RegexOptions.Compiled | RegexOptions.IgnoreCase); @@ -59,6 +61,19 @@ namespace NzbDrone.Core.Parser name = name.Trim(); var normalizedName = name.Replace('_', ' ').Trim().ToLower(); var result = ParseQualityModifiers(name, normalizedName); + var subMatch = HardcodedSubsRegex.Matches(normalizedName).OfType<Match>().LastOrDefault(); + + if (subMatch != null && subMatch.Success) + { + if (subMatch.Groups["hcsub"].Success) + { + result.HardcodedSubs = subMatch.Groups["hcsub"].Value; + } + else if (subMatch.Groups["hc"].Success) + { + result.HardcodedSubs = "Generic Hardcoded Subs"; + } + } if (RawHDRegex.IsMatch(normalizedName)) { @@ -69,6 +84,7 @@ namespace NzbDrone.Core.Parser var sourceMatch = SourceRegex.Matches(normalizedName).OfType<Match>().LastOrDefault(); var resolution = ParseResolution(normalizedName); var codecRegex = CodecRegex.Match(normalizedName); + if (sourceMatch != null && sourceMatch.Success) { @@ -201,6 +217,8 @@ namespace NzbDrone.Core.Parser } } + + //Anime Bluray matching if (AnimeBlurayRegex.Match(normalizedName).Success) diff --git a/src/NzbDrone.Core/Properties/AssemblyInfo.cs b/src/NzbDrone.Core/Properties/AssemblyInfo.cs index 4593d015a..4290120a6 100644 --- a/src/NzbDrone.Core/Properties/AssemblyInfo.cs +++ b/src/NzbDrone.Core/Properties/AssemblyInfo.cs @@ -11,6 +11,6 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("3C29FEF7-4B07-49ED-822E-1C29DC49BFAB")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] [assembly: InternalsVisibleTo("NzbDrone.Core.Test")] diff --git a/src/NzbDrone.Core/Qualities/QualityModel.cs b/src/NzbDrone.Core/Qualities/QualityModel.cs index a483d22c2..2ecc3cb6f 100644 --- a/src/NzbDrone.Core/Qualities/QualityModel.cs +++ b/src/NzbDrone.Core/Qualities/QualityModel.cs @@ -8,6 +8,7 @@ namespace NzbDrone.Core.Qualities { public Quality Quality { get; set; } public Revision Revision { get; set; } + public string HardcodedSubs { get; set; } [JsonIgnore] public QualitySource QualitySource { get; set; } diff --git a/src/NzbDrone.Core/Queue/QueueService.cs b/src/NzbDrone.Core/Queue/QueueService.cs index d867b4ddb..f03d86ccc 100644 --- a/src/NzbDrone.Core/Queue/QueueService.cs +++ b/src/NzbDrone.Core/Queue/QueueService.cs @@ -64,7 +64,7 @@ namespace NzbDrone.Core.Queue Id = HashConverter.GetHashInt31(string.Format("trackedDownload-{0}", trackedDownload.DownloadItem.DownloadId)), Series = null, Episode = null, - Quality = trackedDownload.RemoteMovie.ParsedEpisodeInfo.Quality, + Quality = trackedDownload.RemoteMovie.ParsedMovieInfo.Quality, Title = trackedDownload.DownloadItem.Title, Size = trackedDownload.DownloadItem.TotalSize, Sizeleft = trackedDownload.DownloadItem.RemainingSize, diff --git a/src/NzbDrone.Core/Tv/Movie.cs b/src/NzbDrone.Core/Tv/Movie.cs index d5b49cda0..e31a66896 100644 --- a/src/NzbDrone.Core/Tv/Movie.cs +++ b/src/NzbDrone.Core/Tv/Movie.cs @@ -16,8 +16,9 @@ namespace NzbDrone.Core.Tv Genres = new List<string>(); Actors = new List<Actor>(); Tags = new HashSet<int>(); + AlternativeTitles = new List<string>(); } - + public int TmdbId { get; set; } public string ImdbId { get; set; } public string Title { get; set; } public string CleanTitle { get; set; } @@ -30,6 +31,7 @@ namespace NzbDrone.Core.Tv public int Runtime { get; set; } public List<MediaCover.MediaCover> Images { get; set; } public string TitleSlug { get; set; } + public string Website { get; set; } public string Path { get; set; } public int Year { get; set; } public Ratings Ratings { get; set; } @@ -44,7 +46,7 @@ namespace NzbDrone.Core.Tv public AddMovieOptions AddOptions { get; set; } public LazyLoaded<MovieFile> MovieFile { get; set; } public int MovieFileId { get; set; } - + public List<string> AlternativeTitles { get; set; } public override string ToString() { return string.Format("[{0}][{1}]", ImdbId, Title.NullSafe()); diff --git a/src/NzbDrone.Core/Tv/MovieRepository.cs b/src/NzbDrone.Core/Tv/MovieRepository.cs index f6c4b0ceb..6c046d190 100644 --- a/src/NzbDrone.Core/Tv/MovieRepository.cs +++ b/src/NzbDrone.Core/Tv/MovieRepository.cs @@ -19,6 +19,21 @@ namespace NzbDrone.Core.Tv public class MovieRepository : BasicRepository<Movie>, IMovieRepository { + private readonly Dictionary<string, string> romanNumeralsMapper = new Dictionary<string, string> + { + { "1", "I"}, + { "2", "II"}, + { "3", "III"}, + { "4", "IV"}, + { "5", "V"}, + { "6", "VI"}, + { "7", "VII"}, + { "8", "VII"}, + { "9", "IX"}, + { "10", "X"}, + + }; //If a movie has more than 10 parts fuck 'em. + public MovieRepository(IMainDatabase database, IEventAggregator eventAggregator) : base(database, eventAggregator) { @@ -33,8 +48,46 @@ namespace NzbDrone.Core.Tv { cleanTitle = cleanTitle.ToLowerInvariant(); - return Query.Where(s => s.CleanTitle == cleanTitle) - .SingleOrDefault(); + var cleanRoman = cleanTitle; + + var cleanNum = cleanTitle; + + foreach (KeyValuePair<string, string> entry in romanNumeralsMapper) + { + string num = entry.Key; + string roman = entry.Value.ToLower(); + + cleanRoman = cleanRoman.Replace(num, roman); + + cleanNum = cleanNum.Replace(roman, num); + } + + var result = Query.Where(s => s.CleanTitle == cleanTitle).SingleOrDefault(); + + if (result == null) + { + result = Query.Where(s => s.CleanTitle == cleanNum).OrWhere(s => s.CleanTitle == cleanRoman).SingleOrDefault(); + + if (result == null) + { + var movies = this.All(); + + result = movies.Where(m => m.AlternativeTitles.Any(t => Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanTitle || + Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanRoman || + Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanNum)).SingleOrDefault(); + + return result; + } + else + { + return result; + } + + } + else + { + return result; + } } public Movie FindByTitle(string cleanTitle, int year) @@ -56,7 +109,7 @@ namespace NzbDrone.Core.Tv return Query.Where(m => m.MovieFileId == fileId).ToList(); } - public void SetFileId(int episodeId, int fileId) + public void SetFileId(int fileId, int episodeId) { SetFields(new Movie { Id = episodeId, MovieFileId = fileId }, movie => movie.MovieFileId); } diff --git a/src/NzbDrone.Core/Tv/MovieService.cs b/src/NzbDrone.Core/Tv/MovieService.cs index e255cb215..e61279cac 100644 --- a/src/NzbDrone.Core/Tv/MovieService.cs +++ b/src/NzbDrone.Core/Tv/MovieService.cs @@ -77,7 +77,7 @@ namespace NzbDrone.Core.Tv _logger.Info("Adding Movie {0} Path: [{1}]", newMovie, newMovie.Path); newMovie.CleanTitle = newMovie.Title.CleanSeriesTitle(); - newMovie.SortTitle = MovieTitleNormalizer.Normalize(newMovie.Title, newMovie.ImdbId); + newMovie.SortTitle = MovieTitleNormalizer.Normalize(newMovie.Title, newMovie.TmdbId); newMovie.Added = DateTime.UtcNow; _movieRepository.Insert(newMovie); diff --git a/src/NzbDrone.Core/Tv/MovieTitleNormalizer.cs b/src/NzbDrone.Core/Tv/MovieTitleNormalizer.cs index fd2f87cd1..c82dd014d 100644 --- a/src/NzbDrone.Core/Tv/MovieTitleNormalizer.cs +++ b/src/NzbDrone.Core/Tv/MovieTitleNormalizer.cs @@ -4,16 +4,16 @@ namespace NzbDrone.Core.Tv { public static class MovieTitleNormalizer { - private readonly static Dictionary<string, string> PreComputedTitles = new Dictionary<string, string> + private readonly static Dictionary<int, string> PreComputedTitles = new Dictionary<int, string> { - { "tt_109823457098", "a to z" }, + { 999999999, "a to z" }, }; - public static string Normalize(string title, string imdbid) + public static string Normalize(string title, int tmdbid) { - if (PreComputedTitles.ContainsKey(imdbid)) + if (PreComputedTitles.ContainsKey(tmdbid)) { - return PreComputedTitles[imdbid]; + return PreComputedTitles[tmdbid]; } return Parser.Parser.NormalizeTitle(title).ToLower(); diff --git a/src/NzbDrone.Core/Tv/RefreshMovieService.cs b/src/NzbDrone.Core/Tv/RefreshMovieService.cs index 7b01bcf87..a4ac0d8ec 100644 --- a/src/NzbDrone.Core/Tv/RefreshMovieService.cs +++ b/src/NzbDrone.Core/Tv/RefreshMovieService.cs @@ -51,7 +51,7 @@ namespace NzbDrone.Core.Tv try { - movieInfo = _movieInfo.GetMovieInfo(movie.ImdbId); + movieInfo = _movieInfo.GetMovieInfo(movie.TmdbId); } catch (MovieNotFoundException) { @@ -59,10 +59,10 @@ namespace NzbDrone.Core.Tv return; } - if (movie.ImdbId != movieInfo.ImdbId) + if (movie.TmdbId != movieInfo.TmdbId) { - _logger.Warn("Movie '{0}' (tvdbid {1}) was replaced with '{2}' (tvdbid {3}), because the original was a duplicate.", movie.Title, movie.ImdbId, movieInfo.Title, movieInfo.ImdbId); - movie.ImdbId = movieInfo.ImdbId; + _logger.Warn("Movie '{0}' (tvdbid {1}) was replaced with '{2}' (tvdbid {3}), because the original was a duplicate.", movie.Title, movie.TmdbId, movieInfo.Title, movieInfo.TmdbId); + movie.TmdbId = movieInfo.TmdbId; } movie.Title = movieInfo.Title; @@ -80,6 +80,8 @@ namespace NzbDrone.Core.Tv movie.Genres = movieInfo.Genres; movie.Certification = movieInfo.Certification; movie.InCinemas = movieInfo.InCinemas; + movie.Website = movieInfo.Website; + movie.AlternativeTitles = movieInfo.AlternativeTitles; movie.Year = movieInfo.Year; try diff --git a/src/NzbDrone.Core/Validation/Paths/MovieExistsValidator.cs b/src/NzbDrone.Core/Validation/Paths/MovieExistsValidator.cs index 88519e41f..5aec9a132 100644 --- a/src/NzbDrone.Core/Validation/Paths/MovieExistsValidator.cs +++ b/src/NzbDrone.Core/Validation/Paths/MovieExistsValidator.cs @@ -18,9 +18,9 @@ namespace NzbDrone.Core.Validation.Paths { if (context.PropertyValue == null) return true; - var imdbid = context.PropertyValue.ToString(); + int tmdbId = (int)context.PropertyValue; - return (!_seriesService.GetAllMovies().Exists(s => s.ImdbId == imdbid)); + return (!_seriesService.GetAllMovies().Exists(s => s.TmdbId == tmdbId)); } } } \ No newline at end of file diff --git a/src/NzbDrone.Host/AccessControl/FirewallAdapter.cs b/src/NzbDrone.Host/AccessControl/FirewallAdapter.cs index 794e9edff..9518a6d9e 100644 --- a/src/NzbDrone.Host/AccessControl/FirewallAdapter.cs +++ b/src/NzbDrone.Host/AccessControl/FirewallAdapter.cs @@ -5,7 +5,7 @@ using NLog; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Core.Configuration; -namespace NzbDrone.Host.AccessControl +namespace Radarr.Host.AccessControl { public interface IFirewallAdapter { diff --git a/src/NzbDrone.Host/AccessControl/NetshProvider.cs b/src/NzbDrone.Host/AccessControl/NetshProvider.cs index 88bcd880c..cca3fc8c5 100644 --- a/src/NzbDrone.Host/AccessControl/NetshProvider.cs +++ b/src/NzbDrone.Host/AccessControl/NetshProvider.cs @@ -2,7 +2,7 @@ using NLog; using NzbDrone.Common.Processes; -namespace NzbDrone.Host.AccessControl +namespace Radarr.Host.AccessControl { public interface INetshProvider { diff --git a/src/NzbDrone.Host/AccessControl/SslAdapter.cs b/src/NzbDrone.Host/AccessControl/SslAdapter.cs index 12784ba87..ed9c3aa95 100644 --- a/src/NzbDrone.Host/AccessControl/SslAdapter.cs +++ b/src/NzbDrone.Host/AccessControl/SslAdapter.cs @@ -3,7 +3,7 @@ using System.Text.RegularExpressions; using NLog; using NzbDrone.Core.Configuration; -namespace NzbDrone.Host.AccessControl +namespace Radarr.Host.AccessControl { public interface ISslAdapter { diff --git a/src/NzbDrone.Host/AccessControl/UrlAcl.cs b/src/NzbDrone.Host/AccessControl/UrlAcl.cs index 51af167a6..8ff7e9602 100644 --- a/src/NzbDrone.Host/AccessControl/UrlAcl.cs +++ b/src/NzbDrone.Host/AccessControl/UrlAcl.cs @@ -1,4 +1,4 @@ -namespace NzbDrone.Host.AccessControl +namespace Radarr.Host.AccessControl { public class UrlAcl { diff --git a/src/NzbDrone.Host/AccessControl/UrlAclAdapter.cs b/src/NzbDrone.Host/AccessControl/UrlAclAdapter.cs index 9493dd276..7c61f4320 100644 --- a/src/NzbDrone.Host/AccessControl/UrlAclAdapter.cs +++ b/src/NzbDrone.Host/AccessControl/UrlAclAdapter.cs @@ -7,7 +7,7 @@ using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; using NzbDrone.Core.Configuration; -namespace NzbDrone.Host.AccessControl +namespace Radarr.Host.AccessControl { public interface IUrlAclAdapter { diff --git a/src/NzbDrone.Host/ApplicationModes.cs b/src/NzbDrone.Host/ApplicationModes.cs index aa425948c..3495d8688 100644 --- a/src/NzbDrone.Host/ApplicationModes.cs +++ b/src/NzbDrone.Host/ApplicationModes.cs @@ -1,4 +1,4 @@ -namespace NzbDrone.Host +namespace Radarr.Host { public enum ApplicationModes { diff --git a/src/NzbDrone.Host/ApplicationServer.cs b/src/NzbDrone.Host/ApplicationServer.cs index fdd3c3683..e7235683b 100644 --- a/src/NzbDrone.Host/ApplicationServer.cs +++ b/src/NzbDrone.Host/ApplicationServer.cs @@ -5,9 +5,9 @@ using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Core.Configuration; using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Messaging.Events; -using NzbDrone.Host.Owin; +using Radarr.Host.Owin; -namespace NzbDrone.Host +namespace Radarr.Host { public interface INzbDroneServiceFactory { diff --git a/src/NzbDrone.Host/Bootstrap.cs b/src/NzbDrone.Host/Bootstrap.cs index 24a151eeb..ec3e7a32a 100644 --- a/src/NzbDrone.Host/Bootstrap.cs +++ b/src/NzbDrone.Host/Bootstrap.cs @@ -10,7 +10,7 @@ using NzbDrone.Common.Security; using NzbDrone.Core.Datastore; using NzbDrone.Core.Instrumentation; -namespace NzbDrone.Host +namespace Radarr.Host { public static class Bootstrap { diff --git a/src/NzbDrone.Host/BrowserService.cs b/src/NzbDrone.Host/BrowserService.cs index 1867421cf..cf0a3a313 100644 --- a/src/NzbDrone.Host/BrowserService.cs +++ b/src/NzbDrone.Host/BrowserService.cs @@ -4,7 +4,7 @@ using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Processes; using NzbDrone.Core.Configuration; -namespace NzbDrone.Host +namespace Radarr.Host { public interface IBrowserService { diff --git a/src/NzbDrone.Host/IUserAlert.cs b/src/NzbDrone.Host/IUserAlert.cs index 04db62985..f0ea05ae4 100644 --- a/src/NzbDrone.Host/IUserAlert.cs +++ b/src/NzbDrone.Host/IUserAlert.cs @@ -1,4 +1,4 @@ -namespace NzbDrone.Host +namespace Radarr.Host { public interface IUserAlert { diff --git a/src/NzbDrone.Host/MainAppContainerBuilder.cs b/src/NzbDrone.Host/MainAppContainerBuilder.cs index ca31bb723..a82d3d836 100644 --- a/src/NzbDrone.Host/MainAppContainerBuilder.cs +++ b/src/NzbDrone.Host/MainAppContainerBuilder.cs @@ -6,7 +6,7 @@ using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Http.Dispatchers; using NzbDrone.SignalR; -namespace NzbDrone.Host +namespace Radarr.Host { public class MainAppContainerBuilder : ContainerBuilderBase { @@ -14,7 +14,7 @@ namespace NzbDrone.Host { var assemblies = new List<string> { - "NzbDrone.Host", + "Radarr.Host", "NzbDrone.Common", "NzbDrone.Core", "NzbDrone.Api", diff --git a/src/NzbDrone.Host/NzbDrone.Host.csproj b/src/NzbDrone.Host/NzbDrone.Host.csproj index fa9b7bf42..187c8284e 100644 --- a/src/NzbDrone.Host/NzbDrone.Host.csproj +++ b/src/NzbDrone.Host/NzbDrone.Host.csproj @@ -8,8 +8,8 @@ <ProjectGuid>{95C11A9E-56ED-456A-8447-2C89C1139266}</ProjectGuid> <OutputType>Library</OutputType> <AppDesignerFolder>Properties</AppDesignerFolder> - <RootNamespace>NzbDrone.Host</RootNamespace> - <AssemblyName>NzbDrone.Host</AssemblyName> + <RootNamespace>Radarr.Host</RootNamespace> + <AssemblyName>Radarr.Host</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <TargetFrameworkProfile> diff --git a/src/NzbDrone.Host/Owin/IHostController.cs b/src/NzbDrone.Host/Owin/IHostController.cs index 130b48d4b..74d534b9d 100644 --- a/src/NzbDrone.Host/Owin/IHostController.cs +++ b/src/NzbDrone.Host/Owin/IHostController.cs @@ -1,4 +1,4 @@ -namespace NzbDrone.Host.Owin +namespace Radarr.Host.Owin { public interface IHostController { diff --git a/src/NzbDrone.Host/Owin/MiddleWare/IOwinMiddleWare.cs b/src/NzbDrone.Host/Owin/MiddleWare/IOwinMiddleWare.cs index 1b5e8ce5b..ee33d0df0 100644 --- a/src/NzbDrone.Host/Owin/MiddleWare/IOwinMiddleWare.cs +++ b/src/NzbDrone.Host/Owin/MiddleWare/IOwinMiddleWare.cs @@ -1,6 +1,6 @@ using Owin; -namespace NzbDrone.Host.Owin.MiddleWare +namespace Radarr.Host.Owin.MiddleWare { public interface IOwinMiddleWare { diff --git a/src/NzbDrone.Host/Owin/MiddleWare/NancyMiddleWare.cs b/src/NzbDrone.Host/Owin/MiddleWare/NancyMiddleWare.cs index 89f664864..7b826168b 100644 --- a/src/NzbDrone.Host/Owin/MiddleWare/NancyMiddleWare.cs +++ b/src/NzbDrone.Host/Owin/MiddleWare/NancyMiddleWare.cs @@ -2,7 +2,7 @@ using Nancy.Owin; using Owin; -namespace NzbDrone.Host.Owin.MiddleWare +namespace Radarr.Host.Owin.MiddleWare { public class NancyMiddleWare : IOwinMiddleWare { diff --git a/src/NzbDrone.Host/Owin/MiddleWare/NzbDroneVersionMiddleWare.cs b/src/NzbDrone.Host/Owin/MiddleWare/NzbDroneVersionMiddleWare.cs index a74d9b1d3..a46e357ae 100644 --- a/src/NzbDrone.Host/Owin/MiddleWare/NzbDroneVersionMiddleWare.cs +++ b/src/NzbDrone.Host/Owin/MiddleWare/NzbDroneVersionMiddleWare.cs @@ -4,7 +4,7 @@ using Microsoft.Owin; using NzbDrone.Common.EnvironmentInfo; using Owin; -namespace NzbDrone.Host.Owin.MiddleWare +namespace Radarr.Host.Owin.MiddleWare { public class NzbDroneVersionMiddleWare : IOwinMiddleWare { diff --git a/src/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs b/src/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs index 0df60a326..fa9fe158a 100644 --- a/src/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs +++ b/src/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs @@ -4,7 +4,7 @@ using NzbDrone.Common.Composition; using NzbDrone.SignalR; using Owin; -namespace NzbDrone.Host.Owin.MiddleWare +namespace Radarr.Host.Owin.MiddleWare { public class SignalRMiddleWare : IOwinMiddleWare { diff --git a/src/NzbDrone.Host/Owin/NlogTextWriter.cs b/src/NzbDrone.Host/Owin/NlogTextWriter.cs index 2d04acf1a..b57e26b92 100644 --- a/src/NzbDrone.Host/Owin/NlogTextWriter.cs +++ b/src/NzbDrone.Host/Owin/NlogTextWriter.cs @@ -2,7 +2,7 @@ using System.Text; using NLog; -namespace NzbDrone.Host.Owin +namespace Radarr.Host.Owin { public class NlogTextWriter : TextWriter { diff --git a/src/NzbDrone.Host/Owin/OwinHostController.cs b/src/NzbDrone.Host/Owin/OwinHostController.cs index 82357c24c..a2000974b 100644 --- a/src/NzbDrone.Host/Owin/OwinHostController.cs +++ b/src/NzbDrone.Host/Owin/OwinHostController.cs @@ -1,9 +1,9 @@ using System; using NLog; using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Host.AccessControl; +using Radarr.Host.AccessControl; -namespace NzbDrone.Host.Owin +namespace Radarr.Host.Owin { public class OwinHostController : IHostController { diff --git a/src/NzbDrone.Host/Owin/OwinServiceProvider.cs b/src/NzbDrone.Host/Owin/OwinServiceProvider.cs index c0676cd24..4dd08a2ea 100644 --- a/src/NzbDrone.Host/Owin/OwinServiceProvider.cs +++ b/src/NzbDrone.Host/Owin/OwinServiceProvider.cs @@ -9,10 +9,10 @@ using Microsoft.Owin.Hosting.Services; using Microsoft.Owin.Hosting.Tracing; using NLog; using NzbDrone.Core.Configuration; -using NzbDrone.Host.Owin.MiddleWare; +using Radarr.Host.Owin.MiddleWare; using Owin; -namespace NzbDrone.Host.Owin +namespace Radarr.Host.Owin { public interface IOwinAppFactory { diff --git a/src/NzbDrone.Host/Owin/OwinTraceOutputFactory.cs b/src/NzbDrone.Host/Owin/OwinTraceOutputFactory.cs index 6dc0e57ee..b195ba969 100644 --- a/src/NzbDrone.Host/Owin/OwinTraceOutputFactory.cs +++ b/src/NzbDrone.Host/Owin/OwinTraceOutputFactory.cs @@ -2,7 +2,7 @@ using Microsoft.Owin.Hosting.Tracing; using NLog; -namespace NzbDrone.Host.Owin +namespace Radarr.Host.Owin { public class OwinTraceOutputFactory : ITraceOutputFactory { diff --git a/src/NzbDrone.Host/Owin/PortInUseException.cs b/src/NzbDrone.Host/Owin/PortInUseException.cs index 5c6d7a542..5946bc61a 100644 --- a/src/NzbDrone.Host/Owin/PortInUseException.cs +++ b/src/NzbDrone.Host/Owin/PortInUseException.cs @@ -1,7 +1,7 @@ using System; using NzbDrone.Common.Exceptions; -namespace NzbDrone.Host.Owin +namespace Radarr.Host.Owin { public class PortInUseException : NzbDroneException { diff --git a/src/NzbDrone.Host/PlatformValidation.cs b/src/NzbDrone.Host/PlatformValidation.cs index a4dce7bc8..2082ee814 100644 --- a/src/NzbDrone.Host/PlatformValidation.cs +++ b/src/NzbDrone.Host/PlatformValidation.cs @@ -5,7 +5,7 @@ using NLog; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Instrumentation; -namespace NzbDrone.Host +namespace Radarr.Host { public static class PlatformValidation { diff --git a/src/NzbDrone.Host/Properties/AssemblyInfo.cs b/src/NzbDrone.Host/Properties/AssemblyInfo.cs index dd667bbdd..3291487d0 100644 --- a/src/NzbDrone.Host/Properties/AssemblyInfo.cs +++ b/src/NzbDrone.Host/Properties/AssemblyInfo.cs @@ -5,7 +5,7 @@ using System.Runtime.InteropServices; // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("NzbDrone.exe")] +[assembly: AssemblyTitle("Radarr.exe")] [assembly: Guid("C2172AF4-F9A6-4D91-BAEE-C2E4EE680613")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] diff --git a/src/NzbDrone.Host/Router.cs b/src/NzbDrone.Host/Router.cs index 72d1c8f67..8009ccb70 100644 --- a/src/NzbDrone.Host/Router.cs +++ b/src/NzbDrone.Host/Router.cs @@ -1,7 +1,7 @@ using NLog; using NzbDrone.Common; -namespace NzbDrone.Host +namespace Radarr.Host { public class Router { diff --git a/src/NzbDrone.Host/SingleInstancePolicy.cs b/src/NzbDrone.Host/SingleInstancePolicy.cs index 18de28fb1..8aa3a15dd 100644 --- a/src/NzbDrone.Host/SingleInstancePolicy.cs +++ b/src/NzbDrone.Host/SingleInstancePolicy.cs @@ -4,7 +4,7 @@ using System.Linq; using NLog; using NzbDrone.Common.Processes; -namespace NzbDrone.Host +namespace Radarr.Host { public interface ISingleInstancePolicy { @@ -31,9 +31,9 @@ namespace NzbDrone.Host { if (IsAlreadyRunning()) { - _logger.Warn("Another instance of Sonarr or Radarr is already running."); + _logger.Warn("Another instance of Radarr is already running."); _browserService.LaunchWebUI(); - //throw new TerminateApplicationException("Another instance is already running"); TODO: detect only radarr + throw new TerminateApplicationException("Another instance is already running"); } } diff --git a/src/NzbDrone.Host/SpinService.cs b/src/NzbDrone.Host/SpinService.cs index e2c4e6933..ae35590fd 100644 --- a/src/NzbDrone.Host/SpinService.cs +++ b/src/NzbDrone.Host/SpinService.cs @@ -4,7 +4,7 @@ using NLog.Common; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Processes; -namespace NzbDrone.Host +namespace Radarr.Host { public interface IWaitForExit { diff --git a/src/NzbDrone.Host/TerminateApplicationException.cs b/src/NzbDrone.Host/TerminateApplicationException.cs index 734fb65d2..0c65345c3 100644 --- a/src/NzbDrone.Host/TerminateApplicationException.cs +++ b/src/NzbDrone.Host/TerminateApplicationException.cs @@ -1,6 +1,6 @@ using System; -namespace NzbDrone.Host +namespace Radarr.Host { public class TerminateApplicationException : ApplicationException { diff --git a/src/NzbDrone.Integration.Test/Properties/AssemblyInfo.cs b/src/NzbDrone.Integration.Test/Properties/AssemblyInfo.cs index 5183f6f7e..1e9f85860 100644 --- a/src/NzbDrone.Integration.Test/Properties/AssemblyInfo.cs +++ b/src/NzbDrone.Integration.Test/Properties/AssemblyInfo.cs @@ -21,4 +21,4 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("8a49cb1d-87ac-42f9-a582-607365a6bd79")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] diff --git a/src/NzbDrone.Libraries.Test/Properties/AssemblyInfo.cs b/src/NzbDrone.Libraries.Test/Properties/AssemblyInfo.cs index 8d91461ae..3e164730a 100644 --- a/src/NzbDrone.Libraries.Test/Properties/AssemblyInfo.cs +++ b/src/NzbDrone.Libraries.Test/Properties/AssemblyInfo.cs @@ -21,4 +21,4 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("32ec29e2-40ba-4050-917d-e295d85d4969")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] diff --git a/src/NzbDrone.Mono.Test/Properties/AssemblyInfo.cs b/src/NzbDrone.Mono.Test/Properties/AssemblyInfo.cs index 012007b52..d2eaab331 100644 --- a/src/NzbDrone.Mono.Test/Properties/AssemblyInfo.cs +++ b/src/NzbDrone.Mono.Test/Properties/AssemblyInfo.cs @@ -21,4 +21,4 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("45299d3c-34ff-48ca-9093-de2f037c38ac")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] diff --git a/src/NzbDrone.Mono/Properties/AssemblyInfo.cs b/src/NzbDrone.Mono/Properties/AssemblyInfo.cs index f78631ed8..dfcf8198e 100644 --- a/src/NzbDrone.Mono/Properties/AssemblyInfo.cs +++ b/src/NzbDrone.Mono/Properties/AssemblyInfo.cs @@ -21,4 +21,4 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("01493ea5-494f-43bf-be18-8ae4d0708fc6")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] diff --git a/src/NzbDrone.SignalR/Properties/AssemblyInfo.cs b/src/NzbDrone.SignalR/Properties/AssemblyInfo.cs index 7d5972415..b62df24ed 100644 --- a/src/NzbDrone.SignalR/Properties/AssemblyInfo.cs +++ b/src/NzbDrone.SignalR/Properties/AssemblyInfo.cs @@ -7,4 +7,4 @@ using System.Runtime.InteropServices; [assembly: AssemblyTitle("NzbDrone.SignalR")] [assembly: Guid("98bd985a-4f23-4201-8ed3-f6f3d7f2a5fe")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] diff --git a/src/NzbDrone.Test.Common/NzbDroneRunner.cs b/src/NzbDrone.Test.Common/NzbDroneRunner.cs index 87f3f49e9..d844ea8f7 100644 --- a/src/NzbDrone.Test.Common/NzbDroneRunner.cs +++ b/src/NzbDrone.Test.Common/NzbDroneRunner.cs @@ -32,16 +32,16 @@ namespace NzbDrone.Test.Common { AppData = Path.Combine(TestContext.CurrentContext.TestDirectory, "_intg_" + DateTime.Now.Ticks); - var nzbdroneConsoleExe = "NzbDrone.Console.exe"; + var nzbdroneConsoleExe = "Radarr.Console.exe"; if (OsInfo.IsNotWindows) { - nzbdroneConsoleExe = "NzbDrone.exe"; + nzbdroneConsoleExe = "Radarr.exe"; } if (BuildInfo.IsDebug) { - Start(Path.Combine(TestContext.CurrentContext.TestDirectory, "..\\..\\..\\..\\..\\_output\\NzbDrone.Console.exe")); + Start(Path.Combine(TestContext.CurrentContext.TestDirectory, "..\\..\\..\\..\\..\\_output\\Radarr.Console.exe")); } else { diff --git a/src/NzbDrone.Test.Common/Properties/AssemblyInfo.cs b/src/NzbDrone.Test.Common/Properties/AssemblyInfo.cs index d82d940d5..756e6ff8c 100644 --- a/src/NzbDrone.Test.Common/Properties/AssemblyInfo.cs +++ b/src/NzbDrone.Test.Common/Properties/AssemblyInfo.cs @@ -21,4 +21,4 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("f3e91f6e-d01d-4f20-8255-147cc10f04e3")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] diff --git a/src/NzbDrone.Test.Dummy/Properties/AssemblyInfo.cs b/src/NzbDrone.Test.Dummy/Properties/AssemblyInfo.cs index d2e93dadf..c5823c85b 100644 --- a/src/NzbDrone.Test.Dummy/Properties/AssemblyInfo.cs +++ b/src/NzbDrone.Test.Dummy/Properties/AssemblyInfo.cs @@ -21,4 +21,4 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("7b773a86-574d-48c3-9e89-6f2e0dff714b")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] diff --git a/src/NzbDrone.Update.Test/ProgramFixture.cs b/src/NzbDrone.Update.Test/ProgramFixture.cs index 5d9b7243a..fb3ba1338 100644 --- a/src/NzbDrone.Update.Test/ProgramFixture.cs +++ b/src/NzbDrone.Update.Test/ProgramFixture.cs @@ -34,7 +34,7 @@ namespace NzbDrone.Update.Test [Test] public void should_call_update_with_correct_path() { - var ProcessPath = @"C:\NzbDrone\nzbdrone.exe".AsOsAgnostic(); + var ProcessPath = @"C:\NzbDrone\radarr.exe".AsOsAgnostic(); Mocker.GetMock<IProcessProvider>().Setup(c => c.GetProcessById(12)) .Returns(new ProcessInfo() { StartPath = ProcessPath }); diff --git a/src/NzbDrone.Update.Test/Properties/AssemblyInfo.cs b/src/NzbDrone.Update.Test/Properties/AssemblyInfo.cs index 35dc227d7..d3ec2dcd8 100644 --- a/src/NzbDrone.Update.Test/Properties/AssemblyInfo.cs +++ b/src/NzbDrone.Update.Test/Properties/AssemblyInfo.cs @@ -21,4 +21,4 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("b323e212-2d04-4c7f-9097-c356749ace4d")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] diff --git a/src/NzbDrone.Update.Test/StartNzbDroneService.cs b/src/NzbDrone.Update.Test/StartNzbDroneService.cs index 4cb97c91d..d8f8a5dd9 100644 --- a/src/NzbDrone.Update.Test/StartNzbDroneService.cs +++ b/src/NzbDrone.Update.Test/StartNzbDroneService.cs @@ -32,7 +32,7 @@ namespace NzbDrone.Update.Test Subject.Start(AppType.Service, targetFolder); - Mocker.GetMock<IProcessProvider>().Verify(c => c.SpawnNewProcess("c:\\NzbDrone\\NzbDrone.Console.exe", "/" + StartupContext.NO_BROWSER, null), Times.Once()); + Mocker.GetMock<IProcessProvider>().Verify(c => c.SpawnNewProcess("c:\\NzbDrone\\Radarr.Console.exe", "/" + StartupContext.NO_BROWSER, null), Times.Once()); ExceptionVerification.ExpectedWarns(1); } diff --git a/src/NzbDrone.Update.Test/UpdateProviderStartFixture.cs b/src/NzbDrone.Update.Test/UpdateProviderStartFixture.cs index 6e6456b59..a5a852951 100644 --- a/src/NzbDrone.Update.Test/UpdateProviderStartFixture.cs +++ b/src/NzbDrone.Update.Test/UpdateProviderStartFixture.cs @@ -232,7 +232,7 @@ namespace NzbDrone.Update.Test .Verify(c => c.Start(It.IsAny<string>()), Times.Never()); Mocker.GetMock<IProcessProvider>() - .Verify(c => c.Start(TARGET_FOLDER + "NzbDrone.exe"), Times.Once()); + .Verify(c => c.Start(TARGET_FOLDER + "radarr.exe"), Times.Once()); } diff --git a/src/NzbDrone.Update/NzbDrone.Update.csproj b/src/NzbDrone.Update/NzbDrone.Update.csproj index 2fa4f4bc5..a6721db7a 100644 --- a/src/NzbDrone.Update/NzbDrone.Update.csproj +++ b/src/NzbDrone.Update/NzbDrone.Update.csproj @@ -9,7 +9,7 @@ <OutputType>WinExe</OutputType> <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>NzbDrone.Update</RootNamespace> - <AssemblyName>NzbDrone.Update</AssemblyName> + <AssemblyName>Radarr.Update</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> <TargetFrameworkProfile> </TargetFrameworkProfile> diff --git a/src/NzbDrone.Update/Properties/AssemblyInfo.cs b/src/NzbDrone.Update/Properties/AssemblyInfo.cs index 5a577baf3..84f9d2957 100644 --- a/src/NzbDrone.Update/Properties/AssemblyInfo.cs +++ b/src/NzbDrone.Update/Properties/AssemblyInfo.cs @@ -9,4 +9,4 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("e4560a3d-8053-4d57-a260-bfe52f4cc357")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] diff --git a/src/NzbDrone.Update/UpdateEngine/DetectExistingVersion.cs b/src/NzbDrone.Update/UpdateEngine/DetectExistingVersion.cs index d27190f17..2494e43bc 100644 --- a/src/NzbDrone.Update/UpdateEngine/DetectExistingVersion.cs +++ b/src/NzbDrone.Update/UpdateEngine/DetectExistingVersion.cs @@ -22,7 +22,7 @@ namespace NzbDrone.Update.UpdateEngine { try { - var targetExecutable = Path.Combine(targetFolder, "NzbDrone.exe"); + var targetExecutable = Path.Combine(targetFolder, "Radarr.exe"); if (File.Exists(targetExecutable)) { diff --git a/src/NzbDrone.Update/UpdateEngine/StartNzbDrone.cs b/src/NzbDrone.Update/UpdateEngine/StartNzbDrone.cs index 0a1bc9147..470f28a7a 100644 --- a/src/NzbDrone.Update/UpdateEngine/StartNzbDrone.cs +++ b/src/NzbDrone.Update/UpdateEngine/StartNzbDrone.cs @@ -62,12 +62,12 @@ namespace NzbDrone.Update.UpdateEngine private void StartWinform(string installationFolder) { - Start(installationFolder, "NzbDrone.exe"); + Start(installationFolder, "Radarr.exe"); } private void StartConsole(string installationFolder) { - Start(installationFolder, "NzbDrone.Console.exe"); + Start(installationFolder, "Radarr.Console.exe"); } private void Start(string installationFolder, string fileName) diff --git a/src/NzbDrone.Windows.Test/Properties/AssemblyInfo.cs b/src/NzbDrone.Windows.Test/Properties/AssemblyInfo.cs index c881ae54e..cf513e568 100644 --- a/src/NzbDrone.Windows.Test/Properties/AssemblyInfo.cs +++ b/src/NzbDrone.Windows.Test/Properties/AssemblyInfo.cs @@ -21,4 +21,4 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("372cb8dc-5cdf-4fe4-9e1d-725827889bc7")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] diff --git a/src/NzbDrone.Windows/Properties/AssemblyInfo.cs b/src/NzbDrone.Windows/Properties/AssemblyInfo.cs index bbeee6014..5edd3dee8 100644 --- a/src/NzbDrone.Windows/Properties/AssemblyInfo.cs +++ b/src/NzbDrone.Windows/Properties/AssemblyInfo.cs @@ -21,4 +21,4 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("cea28fa9-43d0-4682-99f2-d364377adbdf")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] diff --git a/src/NzbDrone/MessageBoxUserAlert.cs b/src/NzbDrone/MessageBoxUserAlert.cs index 1b5686864..da371ccda 100644 --- a/src/NzbDrone/MessageBoxUserAlert.cs +++ b/src/NzbDrone/MessageBoxUserAlert.cs @@ -1,5 +1,5 @@ using System.Windows.Forms; -using NzbDrone.Host; +using Radarr.Host; namespace NzbDrone { diff --git a/src/NzbDrone/NzbDrone.csproj b/src/NzbDrone/NzbDrone.csproj index d36a0dfc9..b54e9ea34 100644 --- a/src/NzbDrone/NzbDrone.csproj +++ b/src/NzbDrone/NzbDrone.csproj @@ -9,7 +9,7 @@ <OutputType>WinExe</OutputType> <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>NzbDrone</RootNamespace> - <AssemblyName>NzbDrone</AssemblyName> + <AssemblyName>Radarr</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <TargetFrameworkProfile> diff --git a/src/NzbDrone/Properties/AssemblyInfo.cs b/src/NzbDrone/Properties/AssemblyInfo.cs index c1bca6872..08e0a3d5c 100644 --- a/src/NzbDrone/Properties/AssemblyInfo.cs +++ b/src/NzbDrone/Properties/AssemblyInfo.cs @@ -5,7 +5,7 @@ using System.Runtime.InteropServices; // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("NzbDrone.exe")] +[assembly: AssemblyTitle("Radarr.exe")] [assembly: Guid("67AADCD9-89AA-4D95-8281-3193740E70E5")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] diff --git a/src/NzbDrone/SysTray/SysTrayApp.cs b/src/NzbDrone/SysTray/SysTrayApp.cs index 5e3359bcb..5cf523101 100644 --- a/src/NzbDrone/SysTray/SysTrayApp.cs +++ b/src/NzbDrone/SysTray/SysTrayApp.cs @@ -4,7 +4,7 @@ using System.Windows.Forms; using NLog; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Processes; -using NzbDrone.Host; +using Radarr.Host; namespace NzbDrone.SysTray { diff --git a/src/NzbDrone/WindowsApp.cs b/src/NzbDrone/WindowsApp.cs index b99f3d134..8cd0fdc6f 100644 --- a/src/NzbDrone/WindowsApp.cs +++ b/src/NzbDrone/WindowsApp.cs @@ -3,7 +3,7 @@ using System.Windows.Forms; using NLog; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Instrumentation; -using NzbDrone.Host; +using Radarr.Host; using NzbDrone.SysTray; namespace NzbDrone diff --git a/src/ServiceHelpers/ServiceInstall/Properties/AssemblyInfo.cs b/src/ServiceHelpers/ServiceInstall/Properties/AssemblyInfo.cs index 63a2e4bc0..43733ae42 100644 --- a/src/ServiceHelpers/ServiceInstall/Properties/AssemblyInfo.cs +++ b/src/ServiceHelpers/ServiceInstall/Properties/AssemblyInfo.cs @@ -7,4 +7,4 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("13976baa-e5ba-42b2-8ad7-8d568b68a53b")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] diff --git a/src/ServiceHelpers/ServiceInstall/ServiceHelper.cs b/src/ServiceHelpers/ServiceInstall/ServiceHelper.cs index 78e881170..5e8880978 100644 --- a/src/ServiceHelpers/ServiceInstall/ServiceHelper.cs +++ b/src/ServiceHelpers/ServiceInstall/ServiceHelper.cs @@ -8,7 +8,7 @@ namespace ServiceInstall { public static class ServiceHelper { - private static string NzbDroneExe => Path.Combine(new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName, "NzbDrone.Console.exe"); + private static string NzbDroneExe => Path.Combine(new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName, "Radarr.Console.exe"); private static bool IsAnAdministrator() { @@ -20,7 +20,7 @@ namespace ServiceInstall { if (!File.Exists(NzbDroneExe)) { - Console.WriteLine("Unable to find NzbDrone.Console.exe in the current directory."); + Console.WriteLine("Unable to find Radarr.Console.exe in the current directory."); return; } diff --git a/src/ServiceHelpers/ServiceUninstall/Properties/AssemblyInfo.cs b/src/ServiceHelpers/ServiceUninstall/Properties/AssemblyInfo.cs index c5e087a13..76b8d29ce 100644 --- a/src/ServiceHelpers/ServiceUninstall/Properties/AssemblyInfo.cs +++ b/src/ServiceHelpers/ServiceUninstall/Properties/AssemblyInfo.cs @@ -4,4 +4,4 @@ using System.Runtime.InteropServices; [assembly: AssemblyTitle("UninstallService")] [assembly: Guid("0a964b21-9de9-40b3-9378-0474fd5f21a8")] -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("0.1.0.*")] diff --git a/src/ServiceHelpers/ServiceUninstall/ServiceHelper.cs b/src/ServiceHelpers/ServiceUninstall/ServiceHelper.cs index e5fedb19e..1a046b1b3 100644 --- a/src/ServiceHelpers/ServiceUninstall/ServiceHelper.cs +++ b/src/ServiceHelpers/ServiceUninstall/ServiceHelper.cs @@ -8,7 +8,7 @@ namespace ServiceUninstall { public static class ServiceHelper { - private static string NzbDroneExe => Path.Combine(new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName, "NzbDrone.Console.exe"); + private static string NzbDroneExe => Path.Combine(new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName, "Radarr.Console.exe"); private static bool IsAnAdministrator() { @@ -20,7 +20,7 @@ namespace ServiceUninstall { if (!File.Exists(NzbDroneExe)) { - Console.WriteLine("Unable to find NzbDrone.exe in the current directory."); + Console.WriteLine("Unable to find Radarr.exe in the current directory."); return; } diff --git a/src/UI/AddMovies/AddMoviesView.js b/src/UI/AddMovies/AddMoviesView.js index e83aad42f..ed4c5e2e8 100644 --- a/src/UI/AddMovies/AddMoviesView.js +++ b/src/UI/AddMovies/AddMoviesView.js @@ -125,7 +125,7 @@ module.exports = Marionette.Layout.extend({ } else if (!this.isExisting) { - this.resultCollectionView.setExisting(options.movie.get('imdbId')) + this.resultCollectionView.setExisting(options.movie.get('tmdbId')) /*this.collection.term = ''; this.collection.reset(); this._clearResults(); diff --git a/src/UI/AddMovies/SearchResultCollectionView.js b/src/UI/AddMovies/SearchResultCollectionView.js index ac1257697..1d8c2df89 100644 --- a/src/UI/AddMovies/SearchResultCollectionView.js +++ b/src/UI/AddMovies/SearchResultCollectionView.js @@ -21,8 +21,8 @@ module.exports = Marionette.CollectionView.extend({ return this.showing >= this.collection.length; }, - setExisting : function(imdbid) { - var movies = this.collection.where({ imdbId : imdbid }); + setExisting : function(tmdbid) { + var movies = this.collection.where({ tmdbId : tmdbid }); console.warn(movies) //debugger; if (movies.length > 0) { diff --git a/src/UI/AddMovies/SearchResultView.js b/src/UI/AddMovies/SearchResultView.js index a01a41e3e..7a2a4a6ac 100644 --- a/src/UI/AddMovies/SearchResultView.js +++ b/src/UI/AddMovies/SearchResultView.js @@ -91,7 +91,7 @@ var view = Marionette.ItemView.extend({ }, _configureTemplateHelpers : function() { - var existingMovies = MoviesCollection.where({ imdbId : this.model.get('imdbId') }); + var existingMovies = MoviesCollection.where({ tmdbId : this.model.get('tmdbId') }); console.log(existingMovies) if (existingMovies.length > 0) { this.templateHelpers.existing = existingMovies[0].toJSON(); diff --git a/src/UI/Cells/EditionCell.js b/src/UI/Cells/EditionCell.js new file mode 100644 index 000000000..c110807f5 --- /dev/null +++ b/src/UI/Cells/EditionCell.js @@ -0,0 +1,41 @@ +var Backgrid = require('backgrid'); +var Marionette = require('marionette'); +require('bootstrap'); + +module.exports = Backgrid.Cell.extend({ + className : 'edition-cell', + //template : 'Cells/EditionCellTemplate', + + render : function() { + + var edition = this.model.get(this.column.get('name')); + if (!edition) { + return this; + } + var cut = false; + + if (edition.toLowerCase().contains("cut")) { + cut = true; + } + + //this.templateFunction = Marionette.TemplateCache.get(this.template); + + //var html = this.templateFunction(edition); + if (cut) { + this.$el.html('<i class="icon-sonarr-form-cut"/ title="{0}">'.format(edition)); + } else { + this.$el.html('<i class="icon-sonarr-form-special"/ title="{0}">'.format(edition)); + } + + /*this.$el.popover({ + content : html, + html : true, + trigger : 'hover', + title : this.column.get('title'), + placement : 'left', + container : this.$el + });*/ + + return this; + } +}); diff --git a/src/UI/Cells/EditionCellTemplate.hbs b/src/UI/Cells/EditionCellTemplate.hbs new file mode 100644 index 000000000..9b4f43449 --- /dev/null +++ b/src/UI/Cells/EditionCellTemplate.hbs @@ -0,0 +1,5 @@ +<ul> + <li> + {{this}} + </li> +</ul> diff --git a/src/UI/Cells/EpisodeProgressCell.js b/src/UI/Cells/EpisodeProgressCell.js index 6208040c4..84f39cb8a 100644 --- a/src/UI/Cells/EpisodeProgressCell.js +++ b/src/UI/Cells/EpisodeProgressCell.js @@ -25,4 +25,4 @@ module.exports = NzbDroneCell.extend({ return this; } -}); \ No newline at end of file +}); diff --git a/src/UI/Cells/EventTypeCell.js b/src/UI/Cells/EventTypeCell.js index 4ca9a85ae..5426c7efb 100644 --- a/src/UI/Cells/EventTypeCell.js +++ b/src/UI/Cells/EventTypeCell.js @@ -13,23 +13,23 @@ module.exports = NzbDroneCell.extend({ switch (this.cellValue.get('eventType')) { case 'grabbed': icon = 'icon-sonarr-downloading'; - toolTip = 'Episode grabbed from {0} and sent to download client'.format(this.cellValue.get('data').indexer); + toolTip = 'Movie grabbed from {0} and sent to download client'.format(this.cellValue.get('data').indexer); break; case 'seriesFolderImported': icon = 'icon-sonarr-hdd'; - toolTip = 'Existing episode file added to library'; + toolTip = 'Existing movie file added to library'; break; case 'downloadFolderImported': icon = 'icon-sonarr-imported'; - toolTip = 'Episode downloaded successfully and picked up from download client'; + toolTip = 'Movie downloaded successfully and picked up from download client'; break; case 'downloadFailed': icon = 'icon-sonarr-download-failed'; - toolTip = 'Episode download failed'; + toolTip = 'Movie download failed'; break; case 'episodeFileDeleted': icon = 'icon-sonarr-deleted'; - toolTip = 'Episode file deleted'; + toolTip = 'Movie file deleted'; break; default: icon = 'icon-sonarr-unknown'; @@ -41,4 +41,4 @@ module.exports = NzbDroneCell.extend({ return this; } -}); \ No newline at end of file +}); diff --git a/src/UI/Cells/InCinemasCell.js b/src/UI/Cells/InCinemasCell.js new file mode 100644 index 000000000..a49690f61 --- /dev/null +++ b/src/UI/Cells/InCinemasCell.js @@ -0,0 +1,16 @@ +var TemplatedCell = require('./TemplatedCell'); + +module.exports = TemplatedCell.extend({ + className : 'in-cinemas-cell', + + render : function() { + var monthNames = ["January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" + ]; + var cinemasDate = new Date(this.model.get("inCinemas")); + var year = cinemasDate.getFullYear(); + var month = monthNames[cinemasDate.getMonth()]; + this.$el.html(month + " " + year); //Hack, but somehow handlebar helper does not work. + return this; + } +}); diff --git a/src/UI/Cells/MovieActionCell.js b/src/UI/Cells/MovieActionCell.js new file mode 100644 index 000000000..7f11e0ef5 --- /dev/null +++ b/src/UI/Cells/MovieActionCell.js @@ -0,0 +1,45 @@ +var vent = require('vent'); +var NzbDroneCell = require('./NzbDroneCell'); +var CommandController = require('../Commands/CommandController'); + +module.exports = NzbDroneCell.extend({ + className : 'series-actions-cell', + + ui : { + refresh : '.x-refresh' + }, + + events : { + 'click .x-edit' : '_editSeries', + 'click .x-refresh' : '_refreshSeries' + }, + + render : function() { + this.$el.empty(); + + this.$el.html('<i class="icon-sonarr-refresh x-refresh hidden-xs" title="" data-original-title="Update movie info and scan disk"></i> ' + + '<i class="icon-sonarr-edit x-edit" title="" data-original-title="Edit Movie"></i>'); + + CommandController.bindToCommand({ + element : this.$el.find('.x-refresh'), + command : { + name : 'refreshMovie', + movieId : this.model.get('id') + } + }); + + this.delegateEvents(); + return this; + }, + + _editSeries : function() { + vent.trigger(vent.Commands.EditSeriesCommand, { series : this.model }); + }, + + _refreshSeries : function() { + CommandController.Execute('refreshMovie', { + name : 'refreshMovie', + movieId : this.model.id + }); + } +}); diff --git a/src/UI/Cells/MovieLinksCell.js b/src/UI/Cells/MovieLinksCell.js new file mode 100644 index 000000000..ff8643d1a --- /dev/null +++ b/src/UI/Cells/MovieLinksCell.js @@ -0,0 +1,6 @@ +var TemplatedCell = require('./TemplatedCell'); + +module.exports = TemplatedCell.extend({ + className : 'movie-links-cell', + template : 'Cells/MovieLinksTemplate' +}); diff --git a/src/UI/Cells/MovieLinksTemplate.hbs b/src/UI/Cells/MovieLinksTemplate.hbs new file mode 100644 index 000000000..01a3f52e2 --- /dev/null +++ b/src/UI/Cells/MovieLinksTemplate.hbs @@ -0,0 +1,11 @@ +<span class="series-info-links"> + <!--<a href="{{traktUrl}}" class="label label-info">Trakt</a>--> + {{#if website}} + <a href="{{homepage}}" class="label label-info">Homepage</a> + {{/if}} + <a href="{{tmdbUrl}}" class="label label-info">The Movie DB</a> + + {{#if imdbId}} + <a href="{{imdbUrl}}" class="label label-info">IMDB</a> + {{/if}} +</span> diff --git a/src/UI/Cells/MovieStatusCell.js b/src/UI/Cells/MovieStatusCell.js new file mode 100644 index 000000000..126afeb98 --- /dev/null +++ b/src/UI/Cells/MovieStatusCell.js @@ -0,0 +1,46 @@ +var NzbDroneCell = require('./NzbDroneCell'); + +module.exports = NzbDroneCell.extend({ + className : 'movie-status-cell', + + render : function() { + this.$el.empty(); + var monitored = this.model.get('monitored'); + var status = this.model.get('status'); + var inCinemas = this.model.get("inCinemas"); + var date = new Date(inCinemas); + var timeSince = new Date().getTime() - date.getTime(); + var numOfMonths = timeSince / 1000 / 60 / 60 / 24 / 30; + + if (status === 'released') { + this.$el.html('<i class="icon-sonarr-movie-released grid-icon" title="Released"></i>'); + this._setStatusWeight(3); + } + + if (numOfMonths > 3) { + this.$el.html('<i class="icon-sonarr-movie-released grid-icon" title="Released"></i>');//TODO: Update for PreDB.me + this._setStatusWeight(2); + } + + if (numOfMonths < 3) { + this.$el.html('<i class="icon-sonarr-movie-cinemas grid-icon" title="In Cinemas"></i>'); + this._setStatusWeight(2); + } + + if (status === "announced") { + this.$el.html('<i class="icon-sonarr-movie-announced grid-icon" title="Announced"></i>'); + this._setStatusWeight(1); + } + + else if (!monitored) { + this.$el.html('<i class="icon-sonarr-series-unmonitored grid-icon" title="Not Monitored"></i>'); + this._setStatusWeight(0); + } + + return this; + }, + + _setStatusWeight : function(weight) { + this.model.set('statusWeight', weight, { silent : true }); + } +}); diff --git a/src/UI/Cells/MovieTitleCell2.js b/src/UI/Cells/MovieTitleCell2.js new file mode 100644 index 000000000..f6fec3a35 --- /dev/null +++ b/src/UI/Cells/MovieTitleCell2.js @@ -0,0 +1,6 @@ +var TemplatedCell = require('./TemplatedCell'); + +module.exports = TemplatedCell.extend({ + className : 'series-title-cell', + template : 'Cells/SeriesTitleTemplate', +}); diff --git a/src/UI/Cells/TemplatedCell.js b/src/UI/Cells/TemplatedCell.js index 1299d4e36..eaf8d348e 100644 --- a/src/UI/Cells/TemplatedCell.js +++ b/src/UI/Cells/TemplatedCell.js @@ -18,4 +18,4 @@ module.exports = NzbDroneCell.extend({ this.delegateEvents(); return this; } -}); \ No newline at end of file +}); diff --git a/src/UI/Content/icons.less b/src/UI/Content/icons.less index cce09293a..e06a481c0 100644 --- a/src/UI/Content/icons.less +++ b/src/UI/Content/icons.less @@ -121,6 +121,14 @@ .fa-icon-color(@brand-danger); } +.icon-sonarr-form-cut { + .fa-icon-content(@fa-var-scissors); +} + +.icon-sonarr-form-special { + .fa-icon-content(@fa-var-exclamation-circle); +} + .icon-sonarr-form-info-link { .clickable(); .fa-icon-content(@fa-var-info-circle); @@ -199,6 +207,18 @@ .fa-icon-content(@fa-var-bookmark-o); } +.icon-sonarr-movie-announced { + .fa-icon-content(@fa-var-bullhorn); +} + +.icon-sonarr-movie-released { + .fa-icon-content(@fa-var-file-video-o); +} + +.icon-sonarr-movie-cinemas { + .fa-icon-content(@fa-var-ticket); +} + .icon-sonarr-log-info { .fa-icon-content(@fa-var-info-circle); .fa-icon-color(dodgerblue); @@ -502,4 +522,4 @@ .icon-sonarr-header-rejections { .fa-icon-content(@fa-var-exclamation-circle); -} \ No newline at end of file +} diff --git a/src/UI/Episode/Search/ManualLayout.js b/src/UI/Episode/Search/ManualLayout.js index 58c792063..3b7463e19 100644 --- a/src/UI/Episode/Search/ManualLayout.js +++ b/src/UI/Episode/Search/ManualLayout.js @@ -8,6 +8,7 @@ var DownloadReportCell = require('../../Release/DownloadReportCell'); var AgeCell = require('../../Release/AgeCell'); var ProtocolCell = require('../../Release/ProtocolCell'); var PeersCell = require('../../Release/PeersCell'); +var EditionCell = require('../../Cells/EditionCell'); module.exports = Marionette.Layout.extend({ template : 'Episode/Search/ManualLayoutTemplate', @@ -32,6 +33,12 @@ module.exports = Marionette.Layout.extend({ label : 'Title', cell : ReleaseTitleCell }, + { + name : 'edition', + label : 'Edition', + cell : EditionCell, + title : "Edition" + }, { name : 'indexer', label : 'Indexer', @@ -83,4 +90,4 @@ module.exports = Marionette.Layout.extend({ })); } } -}); \ No newline at end of file +}); diff --git a/src/UI/Handlebars/Helpers/Series.js b/src/UI/Handlebars/Helpers/Series.js index 016279e6e..fbb3a23fc 100644 --- a/src/UI/Handlebars/Helpers/Series.js +++ b/src/UI/Handlebars/Helpers/Series.js @@ -33,7 +33,7 @@ Handlebars.registerHelper('remotePoster', function() { } return new Handlebars.SafeString('<img class="series-poster placeholder-image" src="{0}">'.format(placeholder)); -}) +}); Handlebars.registerHelper('traktUrl', function() { return 'http://trakt.tv/search/tvdb/' + this.tvdbId + '?id_type=show'; @@ -47,6 +47,89 @@ Handlebars.registerHelper('tvdbUrl', function() { return 'http://imdb.com/title/tt' + this.imdbId; }); +Handlebars.registerHelper('tmdbUrl', function() { + return 'https://www.themoviedb.org/movie/' + this.tmdbId; +}); + +Handlebars.registerHelper('homepage', function() { + return this.website; +}); + +Handlebars.registerHelper('alternativeTitlesString', function() { + var titles = this.alternativeTitles; + if (titles.length == 0) { + return ""; + } + if (titles.length == 1) { + return titles[0]; + } + return titles.slice(0,titles.length-1).join(", ") + " and " + titles[titles.length-1]; +}); + +Handlebars.registerHelper('GetStatus', function() { + var monitored = this.monitored; + var status = this.status; + var inCinemas = this.inCinemas; + var date = new Date(inCinemas); + var timeSince = new Date().getTime() - date.getTime(); + var numOfMonths = timeSince / 1000 / 60 / 60 / 24 / 30; + + + if (status === "announced") { + return new Handlebars.SafeString('<i class="icon-sonarr-movie-announced grid-icon" title=""></i> Announced'); + } + + if (numOfMonths < 3) { + + return new Handlebars.SafeString('<i class="icon-sonarr-movie-cinemas grid-icon" title=""></i> In Cinemas'); + } + + if (numOfMonths > 3) { + return new Handlebars.SafeString('<i class="icon-sonarr-movie-released grid-icon" title=""></i> Released');//TODO: Update for PreDB.me + } + + if (status === 'released') { + return new Handlebars.SafeString('<i class="icon-sonarr-movie-released grid-icon" title=""></i> Released'); + } + + else if (!monitored) { + return new Handlebars.SafeString('<i class="icon-sonarr-series-unmonitored grid-icon" title=""></i> Not Monitored'); + } +}) + +Handlebars.registerHelper('GetBannerStatus', function() { + var monitored = this.monitored; + var status = this.status; + var inCinemas = this.inCinemas; + var date = new Date(inCinemas); + var timeSince = new Date().getTime() - date.getTime(); + var numOfMonths = timeSince / 1000 / 60 / 60 / 24 / 30; + + if (status === "announced") { + return new Handlebars.SafeString('<div class="announced-banner"><i class="icon-sonarr-movie-announced grid-icon" title=""></i> Announced</div>'); + } + + if (numOfMonths < 3) { + return new Handlebars.SafeString('<div class="cinemas-banner"><i class="icon-sonarr-movie-cinemas grid-icon" title=""></i> In Cinemas</div>'); + } + + if (status === 'released') { + return new Handlebars.SafeString('<div class="released-banner"><i class="icon-sonarr-movie-released grid-icon" title=""></i> Released</div>'); + } + + if (numOfMonths > 3) { + return new Handlebars.SafeString('<div class="released-banner"><i class="icon-sonarr-movie-released grid-icon" title=""></i> Released</div>');//TODO: Update for PreDB.me + } + + + + + else if (!monitored) { + return new Handlebars.SafeString('<div class="announced-banner"><i class="icon-sonarr-series-unmonitored grid-icon" title=""></i> Not Monitored</div>'); + } +}) + + Handlebars.registerHelper('inCinemas', function() { var monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" @@ -55,7 +138,7 @@ Handlebars.registerHelper('inCinemas', function() { var year = cinemasDate.getFullYear(); var month = monthNames[cinemasDate.getMonth()]; return "In Cinemas " + month + " " + year; -}) +}); Handlebars.registerHelper('tvRageUrl', function() { return 'http://www.tvrage.com/shows/id-' + this.tvRageId; diff --git a/src/UI/Movies/Details/InfoViewTemplate.hbs b/src/UI/Movies/Details/InfoViewTemplate.hbs index 355db6750..1897c841a 100644 --- a/src/UI/Movies/Details/InfoViewTemplate.hbs +++ b/src/UI/Movies/Details/InfoViewTemplate.hbs @@ -1,5 +1,5 @@ <div class="row"> - <div class="col-md-9"> + <div class="col-md-8"> {{profile profileId}} {{#if network}} @@ -27,11 +27,13 @@ <span class="label label-default">Announced</span> {{/if_eq}} </div> - <div class="col-md-3"> + <div class="col-md-4"> <span class="series-info-links"> - <!--<a href="{{traktUrl}}" class="label label-info">Trakt</a> - - <a href="{{tvdbUrl}}" class="label label-info">The TVDB</a>--> + <!--<a href="{{traktUrl}}" class="label label-info">Trakt</a>--> + {{#if website}} + <a href="{{homepage}}" class="label label-info">Homepage</a> + {{/if}} + <a href="{{tmdbUrl}}" class="label label-info">The Movie DB</a> {{#if imdbId}} <a href="{{imdbUrl}}" class="label label-info">IMDB</a> @@ -40,18 +42,12 @@ </div> </div> -{{#if alternateTitles}} +{{#if alternativeTitles}} <div class="row"> <div class="col-md-12"> - {{#each alternateTitles}} - {{#if_eq seasonNumber compare="-1"}} - <span class="label label-default">{{title}}</span> - {{/if_eq}} - - {{#if_eq sceneSeasonNumber compare="-1"}} - <span class="label label-default">{{title}}</span> - {{/if_eq}} - {{/each}} + <span class="alternative-titles"> + Also known as: {{alternativeTitlesString}}. + </span> </div> </div> {{/if}} diff --git a/src/UI/Movies/Details/MoviesDetailsLayout.js b/src/UI/Movies/Details/MoviesDetailsLayout.js index 4396c22de..fc251a049 100644 --- a/src/UI/Movies/Details/MoviesDetailsLayout.js +++ b/src/UI/Movies/Details/MoviesDetailsLayout.js @@ -274,7 +274,7 @@ module.exports = Marionette.Layout.extend({ _showBackdrop : function () { $('body').addClass('backdrop'); - var fanArt = this._getImage('fanart'); + var fanArt = this._getImage('banner'); if (fanArt) { this._backstrech = $.backstretch(fanArt); diff --git a/src/UI/Movies/Details/MoviesDetailsTemplate.hbs b/src/UI/Movies/Details/MoviesDetailsTemplate.hbs index 3a63cd045..9c3f6675a 100644 --- a/src/UI/Movies/Details/MoviesDetailsTemplate.hbs +++ b/src/UI/Movies/Details/MoviesDetailsTemplate.hbs @@ -36,12 +36,18 @@ </div> </div> <div id="movie-info"> - <ul class="nav nav-tabs" id="myTab"> - <li><a href="#movie-history" class="x-movie-history">History</a></li> - <li><a href="#movie-search" class="x-movie-search">Search</a></li> - </ul> - <div class="tab-content"> - <div class="tab-pane" id="movie-history"/> - <div class="tab-pane" id="movie-search"/> + <div class="movie-tabs"> + <div> + <div class="movie-tabs-card"> + <ul class="nav nav-tabs" id="myTab"> + <li><a href="#movie-history" class="x-movie-history">History</a></li> + <li><a href="#movie-search" class="x-movie-search">Search</a></li> + </ul> + <div class="tab-content"> + <div class="tab-pane" id="movie-history"/> + <div class="tab-pane" id="movie-search"/> + </div> + </div> + </div> </div> </div> diff --git a/src/UI/Movies/Index/MoviesIndexLayout.js b/src/UI/Movies/Index/MoviesIndexLayout.js index 68fc34bc5..3e16a7eb7 100644 --- a/src/UI/Movies/Index/MoviesIndexLayout.js +++ b/src/UI/Movies/Index/MoviesIndexLayout.js @@ -5,13 +5,13 @@ var PosterCollectionView = require('./Posters/SeriesPostersCollectionView'); var ListCollectionView = require('./Overview/SeriesOverviewCollectionView'); var EmptyView = require('./EmptyView'); var MoviesCollection = require('../MoviesCollection'); -var RelativeDateCell = require('../../Cells/RelativeDateCell'); -var SeriesTitleCell = require('../../Cells/SeriesTitleCell'); +var InCinemasCell = require('../../Cells/InCinemasCell'); +var MovieTitleCell = require('../../Cells/MovieTitleCell2'); var TemplatedCell = require('../../Cells/TemplatedCell'); var ProfileCell = require('../../Cells/ProfileCell'); -var EpisodeProgressCell = require('../../Cells/EpisodeProgressCell'); -var SeriesActionsCell = require('../../Cells/SeriesActionsCell'); -var SeriesStatusCell = require('../../Cells/SeriesStatusCell'); +var MovieLinksCell = require('../../Cells/MovieLinksCell'); +var MovieActionCell = require('../../Cells/MovieActionCell'); +var MovieStatusCell = require('../../Cells/MovieStatusCell'); var FooterView = require('./FooterView'); var FooterModel = require('./FooterModel'); var ToolbarLayout = require('../../Shared/Toolbar/ToolbarLayout'); @@ -31,46 +31,36 @@ module.exports = Marionette.Layout.extend({ { name : 'statusWeight', label : '', - cell : SeriesStatusCell + cell : MovieStatusCell }, { name : 'title', label : 'Title', - cell : SeriesTitleCell, + cell : MovieTitleCell, cellValue : 'this', sortValue : 'sortTitle' }, - { - name : 'seasonCount', - label : 'Seasons', - cell : 'integer' - }, { name : 'profileId', label : 'Profile', cell : ProfileCell }, { - name : 'network', - label : 'Network', - cell : 'string' - }, - { - name : 'nextAiring', - label : 'Next Airing', - cell : RelativeDateCell + name : 'inCinemas', + label : 'In Cinemas', + cell : InCinemasCell }, { - name : 'percentOfEpisodes', - label : 'Episodes', - cell : EpisodeProgressCell, - className : 'episode-progress-cell' + name : 'this', + label : 'Links', + cell : MovieLinksCell, + className : "movie-links-cell" }, { name : 'this', label : '', sortable : false, - cell : SeriesActionsCell + cell : MovieActionCell } ], diff --git a/src/UI/Movies/Index/Overview/SeriesOverviewItemViewTemplate.hbs b/src/UI/Movies/Index/Overview/SeriesOverviewItemViewTemplate.hbs index ee6ddddee..e16191edb 100644 --- a/src/UI/Movies/Index/Overview/SeriesOverviewItemViewTemplate.hbs +++ b/src/UI/Movies/Index/Overview/SeriesOverviewItemViewTemplate.hbs @@ -1,4 +1,4 @@ -<div class="series-item"> +<div class="movie-item"> <div class="row"> <div class="col-md-2 col-xs-3"> <a href="{{route}}"> @@ -34,21 +34,25 @@ </div> </div> <div class="row"> - <div class="col-md-10 col-xs-8"> - {{#if_eq status compare="ended"}} - <span class="label label-danger">Ended</span> - {{/if_eq}} + <div class="col-md-8 col-xs-8"> + <span class="label label-default">{{GetStatus}}</span> - {{#if nextAiring}} - <span class="label label-default">{{RelativeDate nextAiring}}</span> - {{/if}} - - {{seasonCountHelper}} + <span class="label label-default">{{inCinemas}}</span> {{profile profileId}} </div> - <div class="col-md-2 col-xs-4"> - {{> EpisodeProgressPartial }} + <div class="col-md-4 col-xs-4"> + <span class="movie-info-links"> + <!--<a href="{{traktUrl}}" class="label label-info">Trakt</a>--> + {{#if website}} + <a href="{{homepage}}" class="label label-info">Homepage</a> + {{/if}} + <a href="{{tmdbUrl}}" class="label label-info">The Movie DB</a> + + {{#if imdbId}} + <a href="{{imdbUrl}}" class="label label-info">IMDB</a> + {{/if}} + </span> </div> </div> </div> diff --git a/src/UI/Movies/Index/Posters/SeriesPostersItemViewTemplate.hbs b/src/UI/Movies/Index/Posters/SeriesPostersItemViewTemplate.hbs index 9ba5c0dfb..92e6e3298 100644 --- a/src/UI/Movies/Index/Posters/SeriesPostersItemViewTemplate.hbs +++ b/src/UI/Movies/Index/Posters/SeriesPostersItemViewTemplate.hbs @@ -5,9 +5,7 @@ <i class="icon-sonarr-refresh x-refresh" title="Refresh Movie"/> <i class="icon-sonarr-edit x-edit" title="Edit Movie"/> </div> - {{#unless_eq status compare="released"}} - <div class="ended-banner">Released</div> - {{/unless_eq}} + {{GetBannerStatus}} <a href="{{route}}"> {{poster}} <div class="center title">{{title}}</div> @@ -20,11 +18,15 @@ <div class="center"> <div class="labels"> - {{> EpisodeProgressPartial }} - {{#if nextAiring}} - <span class="label label-default">{{RelativeDate nextAiring}}</span> - {{/if}} + {{#if website}} + <a href="{{homepage}}" class="label label-info">Homepage</a> + {{/if}} + <a href="{{tmdbUrl}}" class="label label-info">The Movie DB</a> + {{#if imdbId}} + <a href="{{imdbUrl}}" class="label label-info">IMDB</a> + {{/if}} + </div> </div> </div> diff --git a/src/UI/Movies/Search/ManualLayout.js b/src/UI/Movies/Search/ManualLayout.js index daae5d781..af5413779 100644 --- a/src/UI/Movies/Search/ManualLayout.js +++ b/src/UI/Movies/Search/ManualLayout.js @@ -8,6 +8,7 @@ var DownloadReportCell = require('../../Release/DownloadReportCell'); var AgeCell = require('../../Release/AgeCell'); var ProtocolCell = require('../../Release/ProtocolCell'); var PeersCell = require('../../Release/PeersCell'); +var EditionCell = require('../../Cells/EditionCell'); module.exports = Marionette.Layout.extend({ template : 'Movies/Search/ManualLayoutTemplate', @@ -32,6 +33,12 @@ module.exports = Marionette.Layout.extend({ label : 'Title', cell : ReleaseTitleCell }, + { + name : 'edition', + label : 'Edition', + cell : EditionCell, + title : "Edition" + }, { name : 'indexer', label : 'Indexer', diff --git a/src/UI/Movies/movies.less b/src/UI/Movies/movies.less index 57ff91ed4..6c19733dc 100644 --- a/src/UI/Movies/movies.less +++ b/src/UI/Movies/movies.less @@ -8,6 +8,22 @@ max-width: 100%; } +.movie-tabs-card { + .card; + .opacity(0.9); + margin : 30px 10px; + padding : 10px 25px; + + .show-hide-episodes { + .clickable(); + text-align : center; + + i { + .clickable(); + } + } +} + .edit-movie-modal, .delete-movie-modal { overflow : visible; @@ -52,6 +68,12 @@ a { color : #000000; } + + .movie-info-links { + a { + color: white; + } + } } .movie-page-header { @@ -104,7 +126,7 @@ .card; .clickable; margin-bottom : 20px; - height : 315px; + height : 324px; .center { display : block; @@ -184,7 +206,39 @@ font-weight: 100; } - .ended-banner { + .announced-banner { + color : #eeeeee; + background-color : #777; + .box-shadow(2px 2px 20px #888888); + -moz-transform-origin : 50% 50%; + -webkit-transform-origin : 50% 50%; + position : absolute; + width : 320px; + top : 200px; + left : -122px; + text-align : center; + .opacity(0.9); + + .transform(rotate(45deg)); + } + + .released-banner { + color : #eeeeee; + background-color : #5cb85c; + .box-shadow(2px 2px 20px #888888); + -moz-transform-origin : 50% 50%; + -webkit-transform-origin : 50% 50%; + position : absolute; + width : 320px; + top : 200px; + left : -122px; + text-align : center; + .opacity(0.9); + + .transform(rotate(45deg)); + } + + .cinemas-banner { color : #eeeeee; background-color : #b94a48; .box-shadow(2px 2px 20px #888888); @@ -249,10 +303,17 @@ } } + .movie-detail-overview { margin-bottom : 50px; } +.alternative-titles { + font-size: 12px; + color: rgba(255, 255, 255, 180); + opacity: .75; +} + .movie-season { .episode-number-cell { diff --git a/src/UI/Release/ReleaseLayout.js b/src/UI/Release/ReleaseLayout.js index 07f4a1af6..9b1791ff6 100644 --- a/src/UI/Release/ReleaseLayout.js +++ b/src/UI/Release/ReleaseLayout.js @@ -7,6 +7,7 @@ var FileSizeCell = require('../Cells/FileSizeCell'); var QualityCell = require('../Cells/QualityCell'); var ApprovalStatusCell = require('../Cells/ApprovalStatusCell'); var LoadingView = require('../Shared/LoadingView'); +var EditionCell = require('../Cells/EditionCell'); module.exports = Marionette.Layout.extend({ template : 'Release/ReleaseLayoutTemplate', @@ -17,6 +18,12 @@ module.exports = Marionette.Layout.extend({ }, columns : [ + { + name : 'edition', + label : 'Edition', + sortable : false, + cell : EditionCell + }, { name : 'indexer', label : 'Indexer', @@ -29,12 +36,12 @@ module.exports = Marionette.Layout.extend({ sortable : true, cell : Backgrid.StringCell }, - { + /*{ name : 'episodeNumbers', episodes : 'episodeNumbers', label : 'season', cell : EpisodeNumberCell - }, + },*/ { name : 'size', label : 'Size', @@ -75,4 +82,4 @@ module.exports = Marionette.Layout.extend({ })); } } -}); \ No newline at end of file +});