diff --git a/src/NzbDrone.Core.Test/MovieTests/AlternativeTitleServiceTests/AlternativeTitleFixture.cs b/src/NzbDrone.Core.Test/MovieTests/AlternativeTitleServiceTests/AlternativeTitleFixture.cs new file mode 100644 index 000000000..31b9edbf0 --- /dev/null +++ b/src/NzbDrone.Core.Test/MovieTests/AlternativeTitleServiceTests/AlternativeTitleFixture.cs @@ -0,0 +1,32 @@ +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Movies.AlternativeTitles; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.MovieTests.AlternativeTitleServiceTests +{ + [TestFixture] + public class AlternativeTitleFixture : CoreTest + { + private AlternativeTitle CreateFakeTitle(SourceType source, int votes) + { + return Builder.CreateNew().With(t => t.SourceType = source).With(t => t.Votes = votes) + .Build(); + } + + [TestCase(SourceType.TMDB, -1, true)] + [TestCase(SourceType.TMDB, 1000, true)] + [TestCase(SourceType.Mappings, 0, false)] + [TestCase(SourceType.Mappings, 4, true)] + [TestCase(SourceType.Mappings, -1, false)] + [TestCase(SourceType.Indexer, 0, true)] + [TestCase(SourceType.User, 0, true)] + public void should_be_trusted(SourceType source, int votes, bool trusted) + { + var fakeTitle = CreateFakeTitle(source, votes); + + fakeTitle.IsTrusted().Should().Be(trusted); + } + } +} diff --git a/src/NzbDrone.Core.Test/MovieTests/AlternativeTitleServiceTests/AlternativeTitleServiceFixture.cs b/src/NzbDrone.Core.Test/MovieTests/AlternativeTitleServiceTests/AlternativeTitleServiceFixture.cs new file mode 100644 index 000000000..bd2b49fcb --- /dev/null +++ b/src/NzbDrone.Core.Test/MovieTests/AlternativeTitleServiceTests/AlternativeTitleServiceFixture.cs @@ -0,0 +1,104 @@ +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Movies; +using NzbDrone.Core.Movies.AlternativeTitles; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.MovieTests.AlternativeTitleServiceTests +{ + [TestFixture] + public class AlternativeTitleServiceFixture : CoreTest + { + private AlternativeTitle _title1; + private AlternativeTitle _title2; + private AlternativeTitle _title3; + + private Movie _movie; + + [SetUp] + public void Setup() + { + var titles = Builder.CreateListOfSize(3).All().With(t => t.MovieId = 0).Build(); + _title1 = titles[0]; + _title2 = titles[1]; + _title3 = titles[2]; + _movie = Builder.CreateNew().With(m => m.CleanTitle = "myothertitle").With(m => m.Id = 1).Build(); + } + + private void GivenExistingTitles(params AlternativeTitle[] titles) + { + Mocker.GetMock().Setup(r => r.FindByMovieId(_movie.Id)) + .Returns(titles.ToList()); + } + + [Test] + public void should_update_insert_remove_titles() + { + var titles = new List {_title2, _title3}; + var updates = new List {_title2}; + var deletes = new List {_title1}; + var inserts = new List {_title3}; + GivenExistingTitles(_title1, _title2); + + Subject.UpdateTitles(titles, _movie); + + Mocker.GetMock().Verify(r => r.InsertMany(inserts), Times.Once()); + Mocker.GetMock().Verify(r => r.UpdateMany(updates), Times.Once()); + Mocker.GetMock().Verify(r => r.DeleteMany(deletes), Times.Once()); + } + + [Test] + public void should_not_insert_duplicates() + { + GivenExistingTitles(); + var titles = new List {_title1, _title1}; + var inserts = new List{ _title1 }; + + Subject.UpdateTitles(titles, _movie); + + Mocker.GetMock().Verify(r => r.InsertMany(inserts), Times.Once()); + } + + [Test] + public void should_not_insert_main_title() + { + GivenExistingTitles(); + var titles = new List{_title1}; + var movie = Builder.CreateNew().With(m => m.CleanTitle = _title1.CleanTitle).Build(); + + Subject.UpdateTitles(titles, movie); + + Mocker.GetMock().Verify(r => r.InsertMany(new List()), Times.Once()); + } + + [Test] + public void should_update_movie_id() + { + GivenExistingTitles(); + var titles = new List {_title1, _title2}; + + Subject.UpdateTitles(titles, _movie); + + _title1.MovieId.Should().Be(_movie.Id); + _title2.MovieId.Should().Be(_movie.Id); + } + + [Test] + public void should_update_with_correct_id() + { + var existingTitle = Builder.CreateNew().With(t => t.Id = 2).Build(); + GivenExistingTitles(existingTitle); + var updateTitle = existingTitle.JsonClone(); + updateTitle.Id = 0; + + Subject.UpdateTitles(new List {updateTitle}, _movie); + + Mocker.GetMock().Verify(r => r.UpdateMany(It.Is>(list => list.First().Id == existingTitle.Id)), Times.Once()); + } + } +} diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 7090bbbb0..43add3761 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -296,6 +296,8 @@ + + diff --git a/src/NzbDrone.Core/Movies/AlternativeTitles/AlternativeTitle.cs b/src/NzbDrone.Core/Movies/AlternativeTitles/AlternativeTitle.cs index dea7e1388..d42d59096 100644 --- a/src/NzbDrone.Core/Movies/AlternativeTitles/AlternativeTitle.cs +++ b/src/NzbDrone.Core/Movies/AlternativeTitles/AlternativeTitle.cs @@ -17,30 +17,30 @@ namespace NzbDrone.Core.Movies.AlternativeTitles public int VoteCount { get; set; } public Language Language { get; set; } public LazyLoaded Movie { get; set; } - - public AlternativeTitle() - { - - } - - public AlternativeTitle(string title, SourceType sourceType = SourceType.TMDB, int sourceId = 0, Language language = Language.English) - { - Title = title; - CleanTitle = title.CleanSeriesTitle(); + + public AlternativeTitle() + { + + } + + public AlternativeTitle(string title, SourceType sourceType = SourceType.TMDB, int sourceId = 0, Language language = Language.English) + { + Title = title; + CleanTitle = title.CleanSeriesTitle(); SourceType = sourceType; SourceId = sourceId; - Language = language; + Language = language; } - public bool IsTrusted(int minVotes = 3) + public bool IsTrusted(int minVotes = 4) { switch (SourceType) { - case SourceType.TMDB: + case SourceType.Mappings: return Votes >= minVotes; default: return true; - } + } } public override bool Equals(object obj) diff --git a/src/NzbDrone.Core/Movies/AlternativeTitles/AlternativeTitleRepository.cs b/src/NzbDrone.Core/Movies/AlternativeTitles/AlternativeTitleRepository.cs index 63c8b53b1..824a69a6d 100644 --- a/src/NzbDrone.Core/Movies/AlternativeTitles/AlternativeTitleRepository.cs +++ b/src/NzbDrone.Core/Movies/AlternativeTitles/AlternativeTitleRepository.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; +using System.Data; using System.Linq; +using Marr.Data; using NzbDrone.Common.Extensions; using NzbDrone.Core.Datastore; using NzbDrone.Core.Messaging.Events; diff --git a/src/NzbDrone.Core/Movies/AlternativeTitles/AlternativeTitleService.cs b/src/NzbDrone.Core/Movies/AlternativeTitles/AlternativeTitleService.cs index 1d697ed12..53e100dd7 100644 --- a/src/NzbDrone.Core/Movies/AlternativeTitles/AlternativeTitleService.cs +++ b/src/NzbDrone.Core/Movies/AlternativeTitles/AlternativeTitleService.cs @@ -3,6 +3,7 @@ using NzbDrone.Core.Configuration; using NzbDrone.Core.Messaging.Events; using System.Collections.Generic; using System.Linq; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Movies.Events; namespace NzbDrone.Core.Movies.AlternativeTitles @@ -13,7 +14,7 @@ namespace NzbDrone.Core.Movies.AlternativeTitles AlternativeTitle AddAltTitle(AlternativeTitle title, Movie movie); List AddAltTitles(List titles, Movie movie); AlternativeTitle GetById(int id); - void DeleteNotEnoughVotes(List mappingsTitles); + List UpdateTitles(List titles, Movie movie); } public class AlternativeTitleService : IAlternativeTitleService, IHandleAsync @@ -63,11 +64,28 @@ namespace NzbDrone.Core.Movies.AlternativeTitles _titleRepo.Delete(title); } - public void DeleteNotEnoughVotes(List mappingsTitles) + public List UpdateTitles(List titles, Movie movie) { - var toRemove = mappingsTitles.Where(t => t.SourceType == SourceType.Mappings && t.Votes < 4); - var realT = _titleRepo.FindBySourceIds(toRemove.Select(t => t.SourceId).ToList()); - _titleRepo.DeleteMany(realT); + int movieId = movie.Id; + // First update the movie ids so we can correlate them later. + titles.ForEach(t => t.MovieId = movieId); + // Then make sure none of them are the same as the main title. + titles = titles.Where(t => t.CleanTitle != movie.CleanTitle).ToList(); + // Then make sure they are all distinct titles + titles = titles.DistinctBy(t => t.CleanTitle).ToList(); + + // Now find titles to delete, update and insert. + var existingTitles = _titleRepo.FindByMovieId(movieId); + + var insert = titles.Where(t => !existingTitles.Contains(t)); + var update = existingTitles.Where(t => titles.Contains(t)); + var delete = existingTitles.Where(t => !titles.Contains(t)); + + _titleRepo.DeleteMany(delete.ToList()); + _titleRepo.UpdateMany(update.ToList()); + _titleRepo.InsertMany(insert.ToList()); + + return titles; } public void HandleAsync(MovieDeletedEvent message) diff --git a/src/NzbDrone.Core/Movies/RefreshMovieService.cs b/src/NzbDrone.Core/Movies/RefreshMovieService.cs index 762cbd243..fb62f23a8 100644 --- a/src/NzbDrone.Core/Movies/RefreshMovieService.cs +++ b/src/NzbDrone.Core/Movies/RefreshMovieService.cs @@ -108,26 +108,16 @@ namespace NzbDrone.Core.Movies _logger.Warn(e, "Couldn't update movie path for " + movie.Path); } - movieInfo.AlternativeTitles = movieInfo.AlternativeTitles.Where(t => t.CleanTitle != movie.CleanTitle) - .DistinctBy(t => t.CleanTitle) - .ExceptBy(t => t.CleanTitle, movie.AlternativeTitles, t => t.CleanTitle, EqualityComparer.Default).ToList(); - try { - movie.AlternativeTitles.AddRange(_titleService.AddAltTitles(movieInfo.AlternativeTitles, movie)); - var mappings = _apiClient.AlternativeTitlesAndYearForMovie(movieInfo.TmdbId); var mappingsTitles = mappings.Item1; - _titleService.DeleteNotEnoughVotes(mappingsTitles); - - mappingsTitles = mappingsTitles.ExceptBy(t => t.CleanTitle, movie.AlternativeTitles, - t => t.CleanTitle, EqualityComparer.Default).ToList(); + mappingsTitles = mappingsTitles.Where(t => t.IsTrusted()).ToList(); - - mappingsTitles = mappingsTitles.Where(t => t.Votes > 3).ToList(); - - movie.AlternativeTitles.AddRange(_titleService.AddAltTitles(mappingsTitles, movie)); + movieInfo.AlternativeTitles.AddRange(mappingsTitles); + + movie.AlternativeTitles = _titleService.UpdateTitles(movieInfo.AlternativeTitles, movie); if (mappings.Item2 != null) {