diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 50d8f9832..1a6c69057 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,6 +1,8 @@ +Please use the search bar and make sure you are not submitting an already submitted issue. + Provide a description of the feature request or bug, the more details the better. When possible include a log! diff --git a/README.md b/README.md index 1a6ccd091..e307f83c1 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,17 @@ [![GitHub pull requests](https://img.shields.io/github/issues-pr/radarr/radarr.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/pulls) [![GNU GPL v3](https://img.shields.io/badge/license-GNU%20GPL%20v3-blue.svg?maxAge=60&style=flat-square)](http://www.gnu.org/licenses/gpl.html) [![Copyright 2010-2017](https://img.shields.io/badge/copyright-2017-blue.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr) +[![Github Releases](https://img.shields.io/github/downloads/Radarr/Radarr/total.svg?maxAge=60&style=flat-square)](https://github.com/Radar/Radarr/releases/latest) +[![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/radarr.svg?maxAge=60&style=flat-square)](https://hub.docker.com/r/linuxserver/radarr/) | Service | Master | Develop | |----------|:---------------------------:|:----------------------------:| | AppVeyor | [![AppVeyor](https://img.shields.io/appveyor/ci/galli-leo/Radarr/master.svg?maxAge=60&style=flat-square)](https://ci.appveyor.com/project/galli-leo/Radarr) | [![AppVeyor](https://img.shields.io/appveyor/ci/galli-leo/Radarr-usby1/develop.svg?maxAge=60&style=flat-square)](https://ci.appveyor.com/project/galli-leo/Radarr-usby1) | | Travis | [![Travis](https://img.shields.io/travis/Radarr/Radarr/master.svg?maxAge=60&style=flat-square)](https://travis-ci.org/Radarr/Radarr) | [![Travis](https://img.shields.io/travis/Radarr/Radarr/develop.svg?maxAge=60&style=flat-square)](https://travis-ci.org/Radarr/Radarr) | -This fork of Sonarr aims to turn it into something like CouchPotato. +A fork of [Sonarr](https://github.com/Sonarr/Sonarr) to work with movies à la Couchpotato. + +**This fork works independently of Sonarr and will not interfere with it.** ## Downloads @@ -18,41 +22,45 @@ This fork of Sonarr aims to turn it into something like CouchPotato. [![AppVeyor Builds](https://img.shields.io/badge/downloads-continuous-green.svg?maxAge=60&style=flat-square)](https://ci.appveyor.com/project/galli-leo/radarr-usby1/build/artifacts) -[![Docker x64](https://img.shields.io/badge/docker-x64-blue.svg?maxAge=60&style=flat-square)](https://store.docker.com/community/images/linuxserver/radarr) -[![Docker armhf](https://img.shields.io/badge/docker-armhf-blue.svg?maxAge=60&style=flat-square)](https://store.docker.com/community/images/lsioarmhf/radarr) -[![Docker aarch64](https://img.shields.io/badge/docker-aarch64-blue.svg?maxAge=60&style=flat-square)](https://store.docker.com/community/images/lsioarmhf/radarr-aarch64) +[![Docker x64](https://img.shields.io/badge/docker-x64-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://store.docker.com/community/images/linuxserver/radarr) +[![Docker armhf](https://img.shields.io/badge/docker-armhf-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://store.docker.com/community/images/lsioarmhf/radarr) +[![Docker aarch64](https://img.shields.io/badge/docker-aarch64-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://store.docker.com/community/images/lsioarmhf/radarr-aarch64) To connect to the UI, fire up your browser and open or . +## Support + +[![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60&style=flat-square)](https://discord.gg/AD3UP37) +[![Reddit](https://img.shields.io/badge/reddit-discussion-FF4500.svg?maxAge=60&style=flat-square)](https://www.reddit.com/r/radarr) +[![GitHub](https://img.shields.io/badge/github-issues-181717.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/issues) + ## Features -### Currently Working +### Current Features -* Adding new movies -* Manually searching for releases of movies -* Automatically searching for releases +* Adding new movies with lots of information, such as trailers, ratings, etc. +* Support for major platforms: Windows, Linux, macOS, Raspberry Pi, etc. +* Can watch for better quality of the movies you have and do an automatic upgrade. *eg. from DVD to Blu-Ray* +* 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 +* Full integration with SABnzbd and NZBGet +* Automatically searching for releases as well as RSS Sync * 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) +* All indexers supported by Sonarr also supported +* New PassThePopcorn Indexer +* QBittorrent, Deluge, rTorrent, Transmission and uTorrent download client (Other clients are coming) * New TorrentPotato Indexer (Works well with [Jackett](https://github.com/Jackett/Jackett)) +* And a beautiful UI ### Planned Features * Scanning PreDB to know when a new release is available * Fixing the other Indexers and download clients -* Importing of Sonarr config - -### Major Features - -* Support for major platforms: Windows, Linux, macOS, Raspberry Pi, etc. -* Can watch for better quality of the movies you have and do an automatic upgrade. *eg. from DVD to Blu-Ray* -* 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 -* Full integration with SABnzbd and NZBGet +* Importing movies from various online sources, such as IMDb Watchlists (A complete list can be found [here](https://github.com/Radarr/Radarr/issues/114)) * Full integration with Kodi, Plex (notification, library update, metadata) -* And a beautiful UI + ## Configuring Development Environment @@ -70,7 +78,8 @@ To connect to the UI, fire up your browser and open or < * Install the required Node Packages `npm install` * Start gulp to monitor your dev environment for any changes that need post processing using `npm start` command. -*Please note: gulp must be running at all times while you are working with Radarr client source files.* +> **Notice** +> Gulp must be running at all times while you are working with Radarr client source files. ### Development diff --git a/src/NzbDrone.Api/Blacklist/BlacklistResource.cs b/src/NzbDrone.Api/Blacklist/BlacklistResource.cs index c3f1c6b1b..f29a2a8cb 100644 --- a/src/NzbDrone.Api/Blacklist/BlacklistResource.cs +++ b/src/NzbDrone.Api/Blacklist/BlacklistResource.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using NzbDrone.Api.Movie; using NzbDrone.Api.REST; using NzbDrone.Core.Qualities; using NzbDrone.Api.Series; @@ -11,13 +12,14 @@ namespace NzbDrone.Api.Blacklist { public int SeriesId { get; set; } public List EpisodeIds { get; set; } + public int MovieId { get; set; } public string SourceTitle { get; set; } public QualityModel Quality { get; set; } public DateTime Date { get; set; } public DownloadProtocol Protocol { get; set; } public string Indexer { get; set; } public string Message { get; set; } - + public MovieResource Movie { get; set; } public SeriesResource Series { get; set; } } @@ -30,7 +32,7 @@ namespace NzbDrone.Api.Blacklist return new BlacklistResource { Id = model.Id, - + MovieId = model.MovieId, SeriesId = model.SeriesId, EpisodeIds = model.EpisodeIds, SourceTitle = model.SourceTitle, @@ -39,7 +41,7 @@ namespace NzbDrone.Api.Blacklist Protocol = model.Protocol, Indexer = model.Indexer, Message = model.Message, - + Movie = model.Movie.ToResource(), Series = model.Series.ToResource() }; } diff --git a/src/NzbDrone.Api/Calendar/CalendarModule.cs b/src/NzbDrone.Api/Calendar/CalendarModule.cs index f403b79c7..804546655 100644 --- a/src/NzbDrone.Api/Calendar/CalendarModule.cs +++ b/src/NzbDrone.Api/Calendar/CalendarModule.cs @@ -2,24 +2,38 @@ using System.Collections.Generic; using System.Linq; using NzbDrone.Api.Episodes; +using NzbDrone.Api.Movie; +using NzbDrone.Core.Datastore.Events; +using NzbDrone.Core.MediaCover; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.MovieStats; +using NzbDrone.Core.Tv; +using NzbDrone.Core.Tv.Events; +using NzbDrone.Core.Validation.Paths; +using NzbDrone.Core.DataAugmentation.Scene; +using NzbDrone.Core.Validation; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Tv; using NzbDrone.SignalR; namespace NzbDrone.Api.Calendar { - public class CalendarModule : EpisodeModuleWithSignalR + public class CalendarModule : MovieModule { - public CalendarModule(IEpisodeService episodeService, - ISeriesService seriesService, - IQualityUpgradableSpecification qualityUpgradableSpecification, - IBroadcastSignalRMessage signalRBroadcaster) - : base(episodeService, seriesService, qualityUpgradableSpecification, signalRBroadcaster, "calendar") + public CalendarModule(IBroadcastSignalRMessage signalR, + IMovieService moviesService, + IMovieStatisticsService moviesStatisticsService, + ISceneMappingService sceneMappingService, + IMapCoversToLocal coverMapper) + : base(signalR, moviesService, moviesStatisticsService, sceneMappingService, coverMapper, "calendar") { + GetResourceAll = GetCalendar; } - private List GetCalendar() + private List GetCalendar() { var start = DateTime.Today; var end = DateTime.Today.AddDays(2); @@ -33,9 +47,9 @@ namespace NzbDrone.Api.Calendar if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value); if (queryIncludeUnmonitored.HasValue) includeUnmonitored = Convert.ToBoolean(queryIncludeUnmonitored.Value); - var resources = MapToResource(_episodeService.EpisodesBetweenDates(start, end, includeUnmonitored), true, true); + var resources = _moviesService.GetMoviesBetweenDates(start, end, includeUnmonitored).Select(MapToResource); - return resources.OrderBy(e => e.AirDateUtc).ToList(); + return resources.OrderBy(e => e.InCinemas).ToList(); } } } diff --git a/src/NzbDrone.Api/Movies/RenameMovieModule.cs b/src/NzbDrone.Api/Movies/RenameMovieModule.cs index 8736cccb0..a55d4ac60 100644 --- a/src/NzbDrone.Api/Movies/RenameMovieModule.cs +++ b/src/NzbDrone.Api/Movies/RenameMovieModule.cs @@ -12,7 +12,7 @@ namespace NzbDrone.Api.Movies private readonly IRenameMovieFileService _renameMovieFileService; public RenameMovieModule(IRenameMovieFileService renameMovieFileService) - : base("rename") + : base("renameMovie") { _renameMovieFileService = renameMovieFileService; diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 47a433a54..de7b4faab 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -264,6 +264,7 @@ + @@ -298,4 +299,4 @@ --> - \ No newline at end of file + diff --git a/src/NzbDrone.Api/Series/MovieModule.cs b/src/NzbDrone.Api/Series/MovieModule.cs index a40695a1c..052bcf319 100644 --- a/src/NzbDrone.Api/Series/MovieModule.cs +++ b/src/NzbDrone.Api/Series/MovieModule.cs @@ -28,7 +28,7 @@ namespace NzbDrone.Api.Movie IHandle { - private readonly IMovieService _moviesService; + protected readonly IMovieService _moviesService; private readonly IMovieStatisticsService _moviesStatisticsService; private readonly IMapCoversToLocal _coverMapper; @@ -78,13 +78,33 @@ namespace NzbDrone.Api.Movie PutValidator.RuleFor(s => s.Path).IsValidPath(); } + public MovieModule(IBroadcastSignalRMessage signalRBroadcaster, + IMovieService moviesService, + IMovieStatisticsService moviesStatisticsService, + ISceneMappingService sceneMappingService, + IMapCoversToLocal coverMapper, + string resource) + : base(signalRBroadcaster, resource) + { + _moviesService = moviesService; + _moviesStatisticsService = moviesStatisticsService; + + _coverMapper = coverMapper; + + GetResourceAll = AllMovie; + GetResourceById = GetMovie; + CreateResource = AddMovie; + UpdateResource = UpdateMovie; + DeleteResource = DeleteMovie; + } + private MovieResource GetMovie(int id) { var movies = _moviesService.GetMovie(id); return MapToResource(movies); } - private MovieResource MapToResource(Core.Tv.Movie movies) + protected MovieResource MapToResource(Core.Tv.Movie movies) { if (movies == null) return null; @@ -181,6 +201,8 @@ namespace NzbDrone.Api.Movie //var mappings = null;//_sceneMappingService.FindByTvdbId(resource.TvdbId); //if (mappings == null) return; + + //Not necessary anymore //resource.AlternateTitles = mappings.Select(v => new AlternateTitleResource { Title = v.Title, SeasonNumber = v.SeasonNumber, SceneSeasonNumber = v.SceneSeasonNumber }).ToList(); } @@ -219,7 +241,7 @@ namespace NzbDrone.Api.Movie public void Handle(MediaCoversUpdatedEvent message) { - //BroadcastResourceChange(ModelAction.Updated, message.Movie.Id); + BroadcastResourceChange(ModelAction.Updated, message.Movie.Id); } } } diff --git a/src/NzbDrone.Api/Series/MovieResource.cs b/src/NzbDrone.Api/Series/MovieResource.cs index c0cc8d623..66b47def1 100644 --- a/src/NzbDrone.Api/Series/MovieResource.cs +++ b/src/NzbDrone.Api/Series/MovieResource.cs @@ -34,6 +34,8 @@ namespace NzbDrone.Api.Movie public string RemotePoster { get; set; } public int Year { get; set; } public bool HasFile { get; set; } + public string YouTubeTrailerId { get; set; } + public string Studio { get; set; } //View & Edit public string Path { get; set; } @@ -144,7 +146,9 @@ namespace NzbDrone.Api.Movie AddOptions = model.AddOptions, AlternativeTitles = model.AlternativeTitles, Ratings = model.Ratings, - MovieFile = movieFile + MovieFile = movieFile, + YouTubeTrailerId = model.YouTubeTrailerId, + Studio = model.Studio }; } @@ -191,7 +195,9 @@ namespace NzbDrone.Api.Movie Added = resource.Added, AddOptions = resource.AddOptions, AlternativeTitles = resource.AlternativeTitles, - Ratings = resource.Ratings + Ratings = resource.Ratings, + YouTubeTrailerId = resource.YouTubeTrailerId, + Studio = resource.Studio }; } diff --git a/src/NzbDrone.Api/Series/SeriesModule.cs b/src/NzbDrone.Api/Series/SeriesModule.cs index 239598912..75e02b249 100644 --- a/src/NzbDrone.Api/Series/SeriesModule.cs +++ b/src/NzbDrone.Api/Series/SeriesModule.cs @@ -236,7 +236,7 @@ namespace NzbDrone.Api.Series public void Handle(MediaCoversUpdatedEvent message) { - BroadcastResourceChange(ModelAction.Updated, message.Series.Id); + //BroadcastResourceChange(ModelAction.Updated, message.Series.Id); } } } diff --git a/src/NzbDrone.Api/Wanted/MissingModule.cs b/src/NzbDrone.Api/Wanted/MissingModule.cs index 9f6215a2e..52470cd1a 100644 --- a/src/NzbDrone.Api/Wanted/MissingModule.cs +++ b/src/NzbDrone.Api/Wanted/MissingModule.cs @@ -12,7 +12,7 @@ namespace NzbDrone.Api.Wanted ISeriesService seriesService, IQualityUpgradableSpecification qualityUpgradableSpecification, IBroadcastSignalRMessage signalRBroadcaster) - : base(episodeService, seriesService, qualityUpgradableSpecification, signalRBroadcaster, "wanted/missing") + : base(episodeService, seriesService, qualityUpgradableSpecification, signalRBroadcaster, "wanted/missing_episodes") { GetResourcePaged = GetMissingEpisodes; } diff --git a/src/NzbDrone.Api/Wanted/MovieMissingModule.cs b/src/NzbDrone.Api/Wanted/MovieMissingModule.cs new file mode 100644 index 000000000..9a46b9818 --- /dev/null +++ b/src/NzbDrone.Api/Wanted/MovieMissingModule.cs @@ -0,0 +1,77 @@ +using NzbDrone.Api.Movie; +using NzbDrone.Api.Movies; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.Tv; +using NzbDrone.Core.Datastore; +using NzbDrone.SignalR; +using NzbDrone.Core.Download; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging.Events; +using System; +using NzbDrone.Core.Datastore.Events; + +namespace NzbDrone.Api.Wanted +{ + class MovieMissingModule : NzbDroneRestModuleWithSignalR, + IHandle, + IHandle + { + protected readonly IMovieService _movieService; + + public MovieMissingModule(IMovieService movieService, + IQualityUpgradableSpecification qualityUpgradableSpecification, + IBroadcastSignalRMessage signalRBroadcaster) + : base(signalRBroadcaster, "wanted/missing") + { + + _movieService = movieService; + GetResourcePaged = GetMissingMovies; + } + + private PagingResource GetMissingMovies(PagingResource pagingResource) + { + var pagingSpec = pagingResource.MapToPagingSpec("physicalRelease", SortDirection.Descending); + + if (pagingResource.FilterKey == "monitored" && pagingResource.FilterValue == "false") + { + pagingSpec.FilterExpression = v => v.Monitored == false; + } + else + { + pagingSpec.FilterExpression = v => v.Monitored == true; + } + + var resource = ApplyToPage(_movieService.MoviesWithoutFiles, pagingSpec, v => MapToResource(v, false)); + + return resource; + } + + private MovieResource GetMovie(int id) + { + var movie = _movieService.GetMovie(id); + var resource = MapToResource(movie, true); + return resource; + } + + private MovieResource MapToResource(Core.Tv.Movie movie, bool includeMovieFile) + { + var resource = movie.ToResource(); + return resource; + } + + public void Handle(MovieGrabbedEvent message) + { + var resource = message.Movie.Movie.ToResource(); + + //add a grabbed field in MovieResource? + //resource.Grabbed = true; + + BroadcastResourceChange(ModelAction.Updated, resource); + } + + public void Handle(MovieDownloadedEvent message) + { + BroadcastResourceChange(ModelAction.Updated, message.Movie.Movie.Id); + } + } +} diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetTests/NzbgetFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetTests/NzbgetFixture.cs index 98eb0d35b..226288464 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetTests/NzbgetFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetTests/NzbgetFixture.cs @@ -92,14 +92,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests protected void GivenFailedDownload() { Mocker.GetMock() - .Setup(s => s.DownloadNzb(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(s => s.DownloadNzb(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns((string)null); } protected void GivenSuccessfulDownload() { Mocker.GetMock() - .Setup(s => s.DownloadNzb(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(s => s.DownloadNzb(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Guid.NewGuid().ToString().Replace("-", "")); } diff --git a/src/NzbDrone.Core.Test/ParserTests/ExtendedQualityParserRegex.cs b/src/NzbDrone.Core.Test/ParserTests/ExtendedQualityParserRegex.cs index cd979fa2a..3cf6c1082 100644 --- a/src/NzbDrone.Core.Test/ParserTests/ExtendedQualityParserRegex.cs +++ b/src/NzbDrone.Core.Test/ParserTests/ExtendedQualityParserRegex.cs @@ -47,5 +47,17 @@ namespace NzbDrone.Core.Test.ParserTests { QualityParser.ParseQuality(title).Revision.Version.Should().Be(version); } + + [TestCase("Deadpool 2016 2160p 4K UltraHD BluRay DTS-HD MA 7 1 x264-Whatevs", 19)] + [TestCase("Deadpool 2016 2160p 4K UltraHD DTS-HD MA 7 1 x264-Whatevs", 16)] + [TestCase("Deadpool 2016 4K 2160p UltraHD BluRay AAC2 0 HEVC x265", 19)] + [TestCase("The Revenant 2015 2160p UHD BluRay DTS x264-Whatevs", 19)] + [TestCase("The Revenant 2015 2160p UHD BluRay FLAC 7 1 x264-Whatevs", 19)] + [TestCase("The Martian 2015 2160p Ultra HD BluRay DTS-HD MA 7 1 x264-Whatevs", 19)] + [TestCase("Into the Inferno 2016 2160p Netflix WEBRip DD5 1 x264-Whatevs", 18)] + public void should_parse_ultrahd_from_title(string title, int version) + { + QualityParser.ParseQuality(title).Quality.Id.Should().Be(version); + } } } diff --git a/src/NzbDrone.Core/Blacklisting/Blacklist.cs b/src/NzbDrone.Core/Blacklisting/Blacklist.cs index 1c0813ac0..18f4f9de2 100644 --- a/src/NzbDrone.Core/Blacklisting/Blacklist.cs +++ b/src/NzbDrone.Core/Blacklisting/Blacklist.cs @@ -11,6 +11,8 @@ namespace NzbDrone.Core.Blacklisting { public int SeriesId { get; set; } public Series Series { get; set; } + public int MovieId { get; set; } + public Movie Movie { get; set; } public List EpisodeIds { get; set; } public string SourceTitle { get; set; } public QualityModel Quality { get; set; } diff --git a/src/NzbDrone.Core/Blacklisting/BlacklistRepository.cs b/src/NzbDrone.Core/Blacklisting/BlacklistRepository.cs index 906f2a92b..4094c1519 100644 --- a/src/NzbDrone.Core/Blacklisting/BlacklistRepository.cs +++ b/src/NzbDrone.Core/Blacklisting/BlacklistRepository.cs @@ -10,7 +10,7 @@ namespace NzbDrone.Core.Blacklisting { List BlacklistedByTitle(int seriesId, string sourceTitle); List BlacklistedByTorrentInfoHash(int seriesId, string torrentInfoHash); - List BlacklistedBySeries(int seriesId); + List BlacklistedByMovie(int seriesId); } public class BlacklistRepository : BasicRepository, IBlacklistRepository @@ -20,15 +20,15 @@ namespace NzbDrone.Core.Blacklisting { } - public List BlacklistedByTitle(int seriesId, string sourceTitle) + public List BlacklistedByTitle(int movieId, string sourceTitle) { - return Query.Where(e => e.SeriesId == seriesId) + return Query.Where(e => e.MovieId == movieId) .AndWhere(e => e.SourceTitle.Contains(sourceTitle)); } - public List BlacklistedByTorrentInfoHash(int seriesId, string torrentInfoHash) + public List BlacklistedByTorrentInfoHash(int movieId, string torrentInfoHash) { - return Query.Where(e => e.SeriesId == seriesId) + return Query.Where(e => e.MovieId == movieId) .AndWhere(e => e.TorrentInfoHash.Contains(torrentInfoHash)); } @@ -37,9 +37,14 @@ namespace NzbDrone.Core.Blacklisting return Query.Where(b => b.SeriesId == seriesId); } + public List BlacklistedByMovie(int movieId) + { + return Query.Where(b => b.MovieId == movieId); + } + protected override SortBuilder GetPagedQuery(QueryBuilder query, PagingSpec pagingSpec) { - var baseQuery = query.Join(JoinType.Inner, h => h.Series, (h, s) => h.SeriesId == s.Id); + var baseQuery = query.Join(JoinType.Inner, h => h.Movie, (h, s) => h.MovieId == s.Id); return base.GetPagedQuery(baseQuery, pagingSpec); } diff --git a/src/NzbDrone.Core/Blacklisting/BlacklistService.cs b/src/NzbDrone.Core/Blacklisting/BlacklistService.cs index 1c0829004..ded7338f3 100644 --- a/src/NzbDrone.Core/Blacklisting/BlacklistService.cs +++ b/src/NzbDrone.Core/Blacklisting/BlacklistService.cs @@ -21,7 +21,7 @@ namespace NzbDrone.Core.Blacklisting IExecute, IHandle, - IHandleAsync + IHandleAsync { private readonly IBlacklistRepository _blacklistRepository; @@ -128,8 +128,9 @@ namespace NzbDrone.Core.Blacklisting { var blacklist = new Blacklist { - SeriesId = message.SeriesId, + SeriesId = 0, EpisodeIds = message.EpisodeIds, + MovieId = message.MovieId, SourceTitle = message.SourceTitle, Quality = message.Quality, Date = DateTime.UtcNow, @@ -144,9 +145,9 @@ namespace NzbDrone.Core.Blacklisting _blacklistRepository.Insert(blacklist); } - public void HandleAsync(SeriesDeletedEvent message) + public void HandleAsync(MovieDeletedEvent message) { - var blacklisted = _blacklistRepository.BlacklistedBySeries(message.Series.Id); + var blacklisted = _blacklistRepository.BlacklistedByMovie(message.Movie.Id); _blacklistRepository.DeleteMany(blacklisted); } diff --git a/src/NzbDrone.Core/Datastore/Migration/119_add_youtube_trailer_id_table .cs b/src/NzbDrone.Core/Datastore/Migration/119_add_youtube_trailer_id_table .cs new file mode 100644 index 000000000..e975f3d9c --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/119_add_youtube_trailer_id_table .cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; +using System.Data; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(119)] + public class add_youtube_trailer_id : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("Movies").AddColumn("YouTubeTrailerId").AsString().Nullable(); + + } + + } +} diff --git a/src/NzbDrone.Core/Datastore/Migration/120_add_studio_to_table.cs b/src/NzbDrone.Core/Datastore/Migration/120_add_studio_to_table.cs new file mode 100644 index 000000000..823688c61 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/120_add_studio_to_table.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; +using System.Data; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(120)] + public class add_studio : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("Movies").AddColumn("Studio").AsString().Nullable(); + } + + } +} diff --git a/src/NzbDrone.Core/Datastore/Migration/121_update_filedate_config.cs b/src/NzbDrone.Core/Datastore/Migration/121_update_filedate_config.cs new file mode 100644 index 000000000..a45b6530d --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/121_update_filedate_config.cs @@ -0,0 +1,67 @@ +using System.Data; +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; +using System.Text; +using System.Text.RegularExpressions; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(121)] + public class update_filedate_config : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Execute.WithConnection(SetTitleSlug); + } + + private void SetTitleSlug(IDbConnection conn, IDbTransaction tran) + { + using (IDbCommand getSeriesCmd = conn.CreateCommand()) + { + getSeriesCmd.Transaction = tran; + getSeriesCmd.CommandText = @"SELECT Id, Value FROM Config WHERE Key = 'filedate'"; + using (IDataReader seriesReader = getSeriesCmd.ExecuteReader()) + { + while (seriesReader.Read()) + { + var id = seriesReader.GetInt32(0); + var value = seriesReader.GetString(1); + + using (IDbCommand updateCmd = conn.CreateCommand()) + { + updateCmd.Transaction = tran; + updateCmd.CommandText = "UPDATE Config SET Value = 'Release' WHERE Id = ?"; + updateCmd.AddParameter(id); + + updateCmd.ExecuteNonQuery(); + } + } + } + } + } + + public static string ToUrlSlug(string value) + { + //First to lower case + value = value.ToLowerInvariant(); + + //Remove all accents + var bytes = Encoding.GetEncoding("Cyrillic").GetBytes(value); + value = Encoding.ASCII.GetString(bytes); + + //Replace spaces + value = Regex.Replace(value, @"\s", "-", RegexOptions.Compiled); + + //Remove invalid chars + value = Regex.Replace(value, @"[^a-z0-9\s-_]", "", RegexOptions.Compiled); + + //Trim dashes from end + value = value.Trim('-', '_'); + + //Replace double occurences of - or _ + value = Regex.Replace(value, @"([-_]){2,}", "$1", RegexOptions.Compiled); + + return value; + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Datastore/Migration/122_add_movieid_to_blacklist.cs b/src/NzbDrone.Core/Datastore/Migration/122_add_movieid_to_blacklist.cs new file mode 100644 index 000000000..79b38bfe2 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/122_add_movieid_to_blacklist.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; +using System.Data; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(122)] + public class add_movieid_to_blacklist : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("Blacklist").AddColumn("MovieId").AsInt32().Nullable().WithDefaultValue(0); + Alter.Table("Blacklist").AlterColumn("SeriesId").AsInt32().Nullable(); + Alter.Table("Blacklist").AlterColumn("EpisodeIds").AsString().Nullable(); + } + + } +} diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs index 67117ca85..aba427cbf 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs @@ -68,22 +68,25 @@ namespace NzbDrone.Core.DecisionEngine private int CompareProtocol(DownloadDecision x, DownloadDecision y) { - var result = CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => - { - var delayProfile = _delayProfileService.BestForTags(remoteEpisode.Series.Tags); - var downloadProtocol = remoteEpisode.Release.DownloadProtocol; - return downloadProtocol == delayProfile.PreferredProtocol; - }); if (x.IsForMovie) { - result = CompareBy(x.RemoteMovie, y.RemoteMovie, remoteEpisode => + return CompareBy(x.RemoteMovie, y.RemoteMovie, remoteEpisode => { var delayProfile = _delayProfileService.BestForTags(remoteEpisode.Movie.Tags); var downloadProtocol = remoteEpisode.Release.DownloadProtocol; return downloadProtocol == delayProfile.PreferredProtocol; }); } + + var result = CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => + { + var delayProfile = _delayProfileService.BestForTags(remoteEpisode.Series.Tags); + var downloadProtocol = remoteEpisode.Release.DownloadProtocol; + return downloadProtocol == delayProfile.PreferredProtocol; + }); + + return result; } @@ -125,8 +128,8 @@ namespace NzbDrone.Core.DecisionEngine private int CompareAgeIfUsenet(DownloadDecision x, DownloadDecision y) { - if (x.RemoteEpisode.Release.DownloadProtocol != DownloadProtocol.Usenet || - y.RemoteEpisode.Release.DownloadProtocol != DownloadProtocol.Usenet) + if (x.RemoteMovie.Release.DownloadProtocol != DownloadProtocol.Usenet || + y.RemoteMovie.Release.DownloadProtocol != DownloadProtocol.Usenet) { return 0; } diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs index ec7513482..b11ee0dd4 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs @@ -32,7 +32,7 @@ namespace NzbDrone.Core.DecisionEngine public List GetRssDecision(List reports) { - return GetDecisions(reports).ToList(); + return GetMovieDecisions(reports).ToList(); } public List GetSearchDecision(List reports, SearchCriteriaBase searchCriteriaBase) @@ -83,7 +83,7 @@ namespace NzbDrone.Core.DecisionEngine { if (parsedEpisodeInfo.Quality.HardcodedSubs.IsNotNullOrWhiteSpace()) { - remoteEpisode.DownloadAllowed = false; + remoteEpisode.DownloadAllowed = true; decision = new DownloadDecision(remoteEpisode, new Rejection("Hardcoded subs found: " + parsedEpisodeInfo.Quality.HardcodedSubs)); } else diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs index 9ba7b8ad2..8b9399ca2 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs @@ -34,11 +34,11 @@ namespace NzbDrone.Core.DecisionEngine public List PrioritizeDecisionsForMovies(List decisions) { return decisions.Where(c => c.RemoteMovie.Movie != null) - /*.GroupBy(c => c.RemoteMovie.Movie.Id, (movieId, downloadDecisions) => + .GroupBy(c => c.RemoteMovie.Movie.Id, (movieId, downloadDecisions) => { return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_delayProfileService)); }) - .SelectMany(c => c)*/ + .SelectMany(c => c) .Union(decisions.Where(c => c.RemoteMovie.Movie == null)) .ToList(); } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs index 82bc2d83c..043b1aa3a 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs @@ -33,7 +33,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) { - throw new NotImplementedException(); + if (_blacklistService.Blacklisted(subject.Movie.Id, subject.Release)) + { + _logger.Debug("{0} is blacklisted, rejecting.", subject.Release.Title); + return Decision.Reject("Release is blacklisted"); + } return Decision.Accept(); } diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs index 754e67abd..19eb08e86 100644 --- a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs +++ b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs @@ -32,9 +32,12 @@ namespace NzbDrone.Core.Download.Clients.Nzbget 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 addpaused = Settings.AddPaused; + + var response = _proxy.DownloadNzb(fileContents, filename, category, priority, addpaused, Settings); if (response == null) { @@ -47,9 +50,12 @@ namespace NzbDrone.Core.Download.Clients.Nzbget protected override string AddFromNzbFile(RemoteMovie remoteMovie, string filename, byte[] fileContents) { var category = Settings.TvCategory; // TODO: Update this to MovieCategory? + var priority = Settings.RecentTvPriority; - var response = _proxy.DownloadNzb(fileContents, filename, category, priority, Settings); + var addpaused = Settings.AddPaused; + + var response = _proxy.DownloadNzb(fileContents, filename, category, priority, addpaused, Settings); if(response == null) { diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetProxy.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetProxy.cs index 0434832b6..7a21b45b1 100644 --- a/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetProxy.cs @@ -11,7 +11,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget { public interface INzbgetProxy { - string DownloadNzb(byte[] nzbData, string title, string category, int priority, NzbgetSettings settings); + string DownloadNzb(byte[] nzbData, string title, string category, int priority, bool addpaused, NzbgetSettings settings); NzbgetGlobalStatus GetGlobalStatus(NzbgetSettings settings); List GetQueue(NzbgetSettings settings); List GetHistory(NzbgetSettings settings); @@ -45,12 +45,12 @@ namespace NzbDrone.Core.Download.Clients.Nzbget return version >= minimumVersion; } - public string DownloadNzb(byte[] nzbData, string title, string category, int priority, NzbgetSettings settings) + public string DownloadNzb(byte[] nzbData, string title, string category, int priority, bool addpaused, NzbgetSettings settings) { if (HasVersion(16, settings)) { var droneId = Guid.NewGuid().ToString().Replace("-", ""); - var response = ProcessRequest(settings, "append", title, nzbData, category, priority, false, false, string.Empty, 0, "all", new string[] { "drone", droneId }); + var response = ProcessRequest(settings, "append", title, nzbData, category, priority, false, addpaused, string.Empty, 0, "all", new string[] { "drone", droneId }); if (response <= 0) { return null; diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetSettings.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetSettings.cs index 3321c7671..aff3f27ff 100644 --- a/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetSettings.cs @@ -57,6 +57,9 @@ namespace NzbDrone.Core.Download.Clients.Nzbget [FieldDefinition(7, Label = "Use SSL", Type = FieldType.Checkbox)] public bool UseSsl { get; set; } + [FieldDefinition(8, Label = "Add Paused", Type = FieldType.Checkbox, HelpText = "This option requires at least NzbGet version 16.0")] + public bool AddPaused { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs index 71e0d806b..040276ed2 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs @@ -124,7 +124,7 @@ namespace NzbDrone.Core.Indexers.Newznab { var pageableRequests = new IndexerPageableRequestChain(); - if (false) + if (SupportsMovieSearch) { pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "movie", string.Format("&imdbid={0}", searchCriteria.Movie.ImdbId.Substring(2)))); //strip off the "tt" - VERY HACKY diff --git a/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoParser.cs b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoParser.cs index fdaf7bb9d..54b5f23c4 100644 --- a/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoParser.cs +++ b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoParser.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Net; using System.Text.RegularExpressions; using NzbDrone.Common.Http; @@ -36,12 +37,11 @@ namespace NzbDrone.Core.Indexers.TorrentPotato torrentInfo.Size = (long)torrent.size*1000*1000; torrentInfo.DownloadUrl = torrent.download_url; torrentInfo.InfoUrl = torrent.details_url; - torrentInfo.PublishDate = new System.DateTime(); + torrentInfo.PublishDate = torrent.publish_date.ToUniversalTime(); torrentInfo.Seeders = torrent.seeders; torrentInfo.Peers = torrent.leechers + torrent.seeders; torrentInfo.Freeleech = torrent.freeleech; - torrentInfo.PublishDate = torrent.publishdate.ToUniversalTime(); - + results.Add(torrentInfo); } diff --git a/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoResponse.cs b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoResponse.cs index b0551ca67..ef4cc4f0b 100644 --- a/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoResponse.cs +++ b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoResponse.cs @@ -21,7 +21,7 @@ namespace NzbDrone.Core.Indexers.TorrentPotato public int size { get; set; } public int leechers { get; set; } public int seeders { get; set; } - public DateTime publishdate { get; set; } + public DateTime publish_date { get; set; } } } diff --git a/src/NzbDrone.Core/Indexers/Torznab/TorznabRssParser.cs b/src/NzbDrone.Core/Indexers/Torznab/TorznabRssParser.cs index 27ad87129..33edfeeb1 100644 --- a/src/NzbDrone.Core/Indexers/Torznab/TorznabRssParser.cs +++ b/src/NzbDrone.Core/Indexers/Torznab/TorznabRssParser.cs @@ -39,7 +39,13 @@ namespace NzbDrone.Core.Indexers.Torznab protected override ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInfo) { var torrentInfo = base.ProcessItem(item, releaseInfo) as TorrentInfo; - torrentInfo.ImdbId = int.Parse(GetImdbId(item).Substring(2)); + if (GetImdbId(item) != null) + { + if (torrentInfo != null) + { + torrentInfo.ImdbId = int.Parse(GetImdbId(item).Substring(2)); + } + } return torrentInfo; } diff --git a/src/NzbDrone.Core/Jobs/TaskManager.cs b/src/NzbDrone.Core/Jobs/TaskManager.cs index 33ba087b4..43af20db5 100644 --- a/src/NzbDrone.Core/Jobs/TaskManager.cs +++ b/src/NzbDrone.Core/Jobs/TaskManager.cs @@ -64,9 +64,9 @@ namespace NzbDrone.Core.Jobs 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}, + // new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName}, new ScheduledTask{ Interval = 6*60, TypeName = typeof(CheckHealthCommand).FullName}, - new ScheduledTask{ Interval = 12*60, TypeName = typeof(RefreshSeriesCommand).FullName}, + new ScheduledTask{ Interval = 24*60, TypeName = typeof(RefreshMovieCommand).FullName}, new ScheduledTask{ Interval = 24*60, TypeName = typeof(HousekeepingCommand).FullName}, new ScheduledTask{ Interval = 7*24*60, TypeName = typeof(BackupCommand).FullName}, @@ -80,6 +80,7 @@ namespace NzbDrone.Core.Jobs { Interval = _configService.DownloadedEpisodesScanInterval, TypeName = typeof(DownloadedEpisodesScanCommand).FullName + //TypeName = typeof(DownloadedMovieScanCommand).FullName }, }; diff --git a/src/NzbDrone.Core/MediaCover/CoverAlreadyExistsSpecification.cs b/src/NzbDrone.Core/MediaCover/CoverAlreadyExistsSpecification.cs index 83f1e13de..6e29988a6 100644 --- a/src/NzbDrone.Core/MediaCover/CoverAlreadyExistsSpecification.cs +++ b/src/NzbDrone.Core/MediaCover/CoverAlreadyExistsSpecification.cs @@ -1,5 +1,8 @@ -using NzbDrone.Common.Disk; +using System; +using NzbDrone.Common.Disk; using NzbDrone.Common.Http; +using System.Drawing; +using NLog; namespace NzbDrone.Core.MediaCover { @@ -12,11 +15,13 @@ namespace NzbDrone.Core.MediaCover { private readonly IDiskProvider _diskProvider; private readonly IHttpClient _httpClient; + private readonly Logger _logger; - public CoverAlreadyExistsSpecification(IDiskProvider diskProvider, IHttpClient httpClient) + public CoverAlreadyExistsSpecification(IDiskProvider diskProvider, IHttpClient httpClient, Logger logger) { _diskProvider = diskProvider; _httpClient = httpClient; + _logger = logger; } public bool AlreadyExists(string url, string path) @@ -26,9 +31,31 @@ namespace NzbDrone.Core.MediaCover return false; } + if (!IsValidGDIPlusImage(path)) + { + _diskProvider.DeleteFile(path); + return false; + } + var headers = _httpClient.Head(new HttpRequest(url)).Headers; var fileSize = _diskProvider.GetFileSize(path); return fileSize == headers.ContentLength; } + + private bool IsValidGDIPlusImage(string filename) + { + try + { + using (var bmp = new Bitmap(filename)) + { + } + return true; + } + catch (Exception ex) + { + _logger.Debug(ex, "Corrupted image found at: {0}. Redownloading...", filename); + return false; + } + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs index 048d04068..9df3b251f 100644 --- a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs +++ b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs @@ -114,7 +114,7 @@ namespace NzbDrone.Core.MediaCover } } - private void EnsureCovers(Movie movie) + private void EnsureCovers(Movie movie, int retried = 0) { foreach (var cover in movie.Images) { @@ -130,7 +130,25 @@ namespace NzbDrone.Core.MediaCover } catch (WebException e) { - _logger.Warn(string.Format("Couldn't download media cover for {0}. {1}", movie, e.Message)); + if (e.Status == WebExceptionStatus.ProtocolError) + { + _logger.Warn(e, "Server returned different code than 200. The poster is probably not available yet."); + return; + } + + _logger.Warn(e, string.Format("Couldn't download media cover for {0}. {1}", movie, e.Message)); + if (retried < 3) + { + retried = +1; + _logger.Warn("Retrying for the {0}. time in ten seconds.", retried); + System.Threading.Thread.Sleep(10*1000); + EnsureCovers(movie, retried); + } + else + { + _logger.Warn(e, "Couldn't download media cover even after retrying five times :(."); + } + } catch (Exception e) { diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedMovie.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedMovie.cs index 1a2c812d6..83d10c448 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedMovie.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedMovie.cs @@ -68,22 +68,22 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport { //check if already imported if (importResults.Select(r => r.ImportDecision.LocalMovie.Movie) - .Select(e => e.Id).Contains(localMovie.Movie.Id)) + .Select(m => m.Id).Contains(localMovie.Movie.Id)) { importResults.Add(new ImportResult(importDecision, "Movie has already been imported")); continue; } - var episodeFile = new MovieFile(); - episodeFile.DateAdded = DateTime.UtcNow; - episodeFile.MovieId = localMovie.Movie.Id; - episodeFile.Path = localMovie.Path.CleanFilePath(); - episodeFile.Size = _diskProvider.GetFileSize(localMovie.Path); - episodeFile.Quality = localMovie.Quality; - episodeFile.MediaInfo = localMovie.MediaInfo; - episodeFile.Movie = localMovie.Movie; - episodeFile.ReleaseGroup = localMovie.ParsedMovieInfo.ReleaseGroup; - episodeFile.Edition = localMovie.ParsedMovieInfo.Edition; + var movieFile = new MovieFile(); + movieFile.DateAdded = DateTime.UtcNow; + movieFile.MovieId = localMovie.Movie.Id; + movieFile.Path = localMovie.Path.CleanFilePath(); + movieFile.Size = _diskProvider.GetFileSize(localMovie.Path); + movieFile.Quality = localMovie.Quality; + movieFile.MediaInfo = localMovie.MediaInfo; + movieFile.Movie = localMovie.Movie; + movieFile.ReleaseGroup = localMovie.ParsedMovieInfo.ReleaseGroup; + movieFile.Edition = localMovie.ParsedMovieInfo.Edition; bool copyOnly; switch (importMode) @@ -102,17 +102,17 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport if (newDownload) { - episodeFile.SceneName = GetSceneName(downloadClientItem, localMovie); + movieFile.SceneName = GetSceneName(downloadClientItem, localMovie); - var moveResult = _episodeFileUpgrader.UpgradeMovieFile(episodeFile, localMovie, copyOnly); + var moveResult = _episodeFileUpgrader.UpgradeMovieFile(movieFile, localMovie, copyOnly); //TODO: Check if this works oldFiles = moveResult.OldFiles; } else { - episodeFile.RelativePath = localMovie.Movie.Path.GetRelativePath(episodeFile.Path); + movieFile.RelativePath = localMovie.Movie.Path.GetRelativePath(movieFile.Path); } - _mediaFileService.Add(episodeFile); + _mediaFileService.Add(movieFile); importResults.Add(new ImportResult(importDecision)); if (newDownload) @@ -122,22 +122,22 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport if (downloadClientItem != null) { - _eventAggregator.PublishEvent(new MovieImportedEvent(localMovie, episodeFile, newDownload, downloadClientItem.DownloadClient, downloadClientItem.DownloadId, downloadClientItem.IsReadOnly)); + _eventAggregator.PublishEvent(new MovieImportedEvent(localMovie, movieFile, newDownload, downloadClientItem.DownloadClient, downloadClientItem.DownloadId, downloadClientItem.IsReadOnly)); } else { - _eventAggregator.PublishEvent(new MovieImportedEvent(localMovie, episodeFile, newDownload)); + _eventAggregator.PublishEvent(new MovieImportedEvent(localMovie, movieFile, newDownload)); } if (newDownload) { - _eventAggregator.PublishEvent(new MovieDownloadedEvent(localMovie, episodeFile, oldFiles)); + _eventAggregator.PublishEvent(new MovieDownloadedEvent(localMovie, movieFile, oldFiles)); } } catch (Exception e) { - _logger.Warn(e, "Couldn't import episode " + localMovie); - importResults.Add(new ImportResult(importDecision, "Failed to import episode")); + _logger.Warn(e, "Couldn't import movie " + localMovie); + importResults.Add(new ImportResult(importDecision, "Failed to import movie")); } } diff --git a/src/NzbDrone.Core/MediaFiles/FileDateType.cs b/src/NzbDrone.Core/MediaFiles/FileDateType.cs index 6d78be960..45a8711b9 100644 --- a/src/NzbDrone.Core/MediaFiles/FileDateType.cs +++ b/src/NzbDrone.Core/MediaFiles/FileDateType.cs @@ -3,7 +3,7 @@ public enum FileDateType { None = 0, - LocalAirDate = 1, - UtcAirDate = 2 + Cinemas = 1, + Release = 2 } } diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs index 37e663ee5..4f6a9389b 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs @@ -111,11 +111,11 @@ namespace NzbDrone.Core.MediaFiles public List FilterExistingFiles(List files, Movie movie) { - var seriesFiles = GetFilesBySeries(movie.Id).Select(f => Path.Combine(movie.Path, f.RelativePath)).ToList(); + var movieFiles = GetFilesByMovie(movie.Id).Select(f => Path.Combine(movie.Path, f.RelativePath)).ToList(); - if (!seriesFiles.Any()) return files; + if (!movieFiles.Any()) return files; - return files.Except(seriesFiles, PathEqualityComparer.Instance).ToList(); + return files.Except(movieFiles, PathEqualityComparer.Instance).ToList(); } public EpisodeFile Get(int id) diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs index 49eb11ca9..3c57b3feb 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using NLog; @@ -18,14 +18,17 @@ namespace NzbDrone.Core.MediaFiles public class MediaFileTableCleanupService : IMediaFileTableCleanupService { private readonly IMediaFileService _mediaFileService; + private readonly IMovieService _movieService; private readonly IEpisodeService _episodeService; private readonly Logger _logger; public MediaFileTableCleanupService(IMediaFileService mediaFileService, + IMovieService movieService, IEpisodeService episodeService, Logger logger) { _mediaFileService = mediaFileService; + _movieService = movieService; _episodeService = episodeService; _logger = logger; } @@ -89,61 +92,39 @@ namespace NzbDrone.Core.MediaFiles public void Clean(Movie movie, List filesOnDisk) { - - //TODO: Update implementation for movies. - var seriesFiles = _mediaFileService.GetFilesBySeries(movie.Id); - var episodes = _episodeService.GetEpisodeBySeries(movie.Id); + var movieFiles = _mediaFileService.GetFilesByMovie(movie.Id); var filesOnDiskKeys = new HashSet(filesOnDisk, PathEqualityComparer.Instance); - foreach (var seriesFile in seriesFiles) + foreach(var movieFile in movieFiles) { - var episodeFile = seriesFile; - var episodeFilePath = Path.Combine(movie.Path, episodeFile.RelativePath); + var movieFilePath = Path.Combine(movie.Path, movieFile.RelativePath); try { - if (!filesOnDiskKeys.Contains(episodeFilePath)) + if (!filesOnDiskKeys.Contains(movieFilePath)) { - _logger.Debug("File [{0}] no longer exists on disk, removing from db", episodeFilePath); - _mediaFileService.Delete(seriesFile, DeleteMediaFileReason.MissingFromDisk); + _logger.Debug("File [{0}] no longer exists on disk, removing from db", movieFilePath); + _mediaFileService.Delete(movieFile, DeleteMediaFileReason.MissingFromDisk); continue; } - if (episodes.None(e => e.EpisodeFileId == episodeFile.Id)) - { - _logger.Debug("File [{0}] is not assigned to any episodes, removing from db", episodeFilePath); - _mediaFileService.Delete(episodeFile, DeleteMediaFileReason.NoLinkedEpisodes); - continue; - } + //var localMovie = _parsingService.GetLocalMovie(movieFile.Path, movie); - // var localEpsiode = _parsingService.GetLocalEpisode(episodeFile.Path, series); - // - // if (localEpsiode == null || episodes.Count != localEpsiode.Episodes.Count) - // { - // _logger.Debug("File [{0}] parsed episodes has changed, removing from db", episodeFile.Path); - // _mediaFileService.Delete(episodeFile); - // continue; - // } + //if (localMovie == null) + //{ + // _logger.Debug("File [{0}] parsed episodes has changed, removing from db", localMovie.Path); + // _mediaFileService.Delete(localMovie); + // continue; + //} } catch (Exception ex) { - var errorMessage = string.Format("Unable to cleanup EpisodeFile in DB: {0}", episodeFile.Id); + var errorMessage = string.Format("Unable to cleanup MovieFile in DB: {0}", movieFile.Id); _logger.Error(ex, errorMessage); } } - - foreach (var e in episodes) - { - var episode = e; - - if (episode.EpisodeFileId > 0 && seriesFiles.None(f => f.Id == episode.EpisodeFileId)) - { - episode.EpisodeFileId = 0; - _episodeService.UpdateEpisode(episode); - } - } } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs index 0148fb03a..dd5b02ec8 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs @@ -48,7 +48,11 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo return AudioChannelPositionsText.ContainsIgnoreCase("LFE") ? AudioChannels - 1 + 0.1m : AudioChannels; } - return AudioChannelPositions.Split('/').Sum(s => decimal.Parse(s, CultureInfo.InvariantCulture)); + decimal channels = 0; + + decimal.TryParse(AudioChannelPositions.Split('/').First(), out channels); + + return channels; } } } diff --git a/src/NzbDrone.Core/MediaFiles/UpdateEpisodeFileService.cs b/src/NzbDrone.Core/MediaFiles/UpdateEpisodeFileService.cs index 67b415c20..f6589f554 100644 --- a/src/NzbDrone.Core/MediaFiles/UpdateEpisodeFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/UpdateEpisodeFileService.cs @@ -49,7 +49,7 @@ namespace NzbDrone.Core.MediaFiles switch (_configService.FileDate) { - case FileDateType.LocalAirDate: + case FileDateType.Release: { var airDate = episodes.First().AirDate; var airTime = series.AirTime; @@ -62,7 +62,7 @@ namespace NzbDrone.Core.MediaFiles return ChangeFileDateToLocalAirDate(episodeFilePath, airDate, airTime); } - case FileDateType.UtcAirDate: + case FileDateType.Cinemas: { var airDateUtc = episodes.First().AirDateUtc; diff --git a/src/NzbDrone.Core/MediaFiles/UpdateMovieFileService.cs b/src/NzbDrone.Core/MediaFiles/UpdateMovieFileService.cs index af45d8831..f282ecd3c 100644 --- a/src/NzbDrone.Core/MediaFiles/UpdateMovieFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/UpdateMovieFileService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -20,7 +20,7 @@ namespace NzbDrone.Core.MediaFiles } public class UpdateMovieFileService : IUpdateMovieFileService, - IHandle + IHandle { private readonly IDiskProvider _diskProvider; private readonly IConfigService _configService; @@ -47,17 +47,67 @@ namespace NzbDrone.Core.MediaFiles { var movieFilePath = Path.Combine(movie.Path, movieFile.RelativePath); + switch (_configService.FileDate) + { + case FileDateType.Release: + { + var airDate = movie.PhysicalRelease; + + if (airDate == null) + { + return false; + } + + return ChangeFileDate(movieFilePath, airDate.Value); + } + + case FileDateType.Cinemas: + { + var airDate = movie.InCinemas; + + if (airDate == null) + { + return false; + } + + return ChangeFileDate(movieFilePath, airDate.Value); + } + } + return false; } - public void Handle(SeriesScannedEvent message) + private bool ChangeFileDate(string filePath, DateTime date) + { + DateTime oldDateTime = _diskProvider.FileGetLastWrite(filePath); + + if (!DateTime.Equals(date, oldDateTime)) + { + try + { + _diskProvider.FileSetLastWriteTime(filePath, date); + _logger.Debug("Date of file [{0}] changed from '{1}' to '{2}'", filePath, oldDateTime, date); + + return true; + } + + catch (Exception ex) + { + _logger.Warn(ex, "Unable to set date of file [" + filePath + "]"); + } + } + + return false; + } + + public void Handle(MovieScannedEvent message) { if (_configService.FileDate == FileDateType.None) { return; } - /* var movies = _movieService.MoviesWithFiles(message.Series.Id); + var movies = _movieService.MoviesWithFiles(message.Movie.Id); var movieFiles = new List(); var updated = new List(); @@ -69,7 +119,7 @@ namespace NzbDrone.Core.MediaFiles movieFiles.Add(movieFile); - if (ChangeFileDate(movieFile, message.Series, moviesInFile)) + if (ChangeFileDate(movieFile, message.Movie)) { updated.Add(movieFile); } @@ -77,13 +127,13 @@ namespace NzbDrone.Core.MediaFiles if (updated.Any()) { - _logger.ProgressDebug("Changed file date for {0} files of {1} in {2}", updated.Count, movieFiles.Count, message.Series.Title); + _logger.ProgressDebug("Changed file date for {0} files of {1} in {2}", updated.Count, movieFiles.Count, message.Movie.Title); } else { - _logger.ProgressDebug("No file dates changed for {0}", message.Series.Title); - }*/ + _logger.ProgressDebug("No file dates changed for {0}", message.Movie.Title); + } } private bool ChangeFileDateToLocalAirDate(string filePath, string fileDate, string fileTime) diff --git a/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs b/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs index b8cd9f36d..b0fde064d 100644 --- a/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs @@ -38,10 +38,13 @@ namespace NzbDrone.Core.MediaFiles public MovieFileMoveResult UpgradeMovieFile(MovieFile episodeFile, LocalMovie localEpisode, bool copyOnly = false) { + _logger.Trace("Upgrading existing episode file."); var moveFileResult = new MovieFileMoveResult(); + localEpisode.Movie.MovieFile.LazyLoad(); var existingFile = localEpisode.Movie.MovieFile; + existingFile.LazyLoad(); - if (existingFile.IsLoaded) + if (existingFile.IsLoaded && existingFile.Value != null) { var file = existingFile.Value; var episodeFilePath = Path.Combine(localEpisode.Movie.Path, file.RelativePath); @@ -55,6 +58,10 @@ namespace NzbDrone.Core.MediaFiles moveFileResult.OldFiles.Add(file); _mediaFileService.Delete(file, DeleteMediaFileReason.Upgrade); } + else + { + _logger.Warn("The existing movie file was not lazy loaded."); + } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs index c0987e04f..469e72776 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs @@ -66,6 +66,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource public int vote_count { get; set; } public AlternativeTitles alternative_titles { get; set; } public ReleaseDatesResource release_dates { get; set; } + public VideosResource videos { get; set; } } public class ReleaseDatesResource @@ -130,4 +131,21 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource public string iso_3166_1 { get; set; } public string title { get; set; } } + + public class VideosResource + { + public List