diff --git a/src/NzbDrone.Api/Movies/MovieModule.cs b/src/NzbDrone.Api/Movies/MovieModule.cs index 30a3c08b7..1c6eb599a 100644 --- a/src/NzbDrone.Api/Movies/MovieModule.cs +++ b/src/NzbDrone.Api/Movies/MovieModule.cs @@ -28,11 +28,13 @@ public class MovieModule : RadarrRestModuleWithSignalR, { private const string TITLE_SLUG_ROUTE = "/titleslug/(?[^/]+)"; - protected readonly IMovieService _moviesService; + private readonly IMovieService _movieService; + private readonly IAddMovieService _addMovieService; private readonly IMapCoversToLocal _coverMapper; public MovieModule(IBroadcastSignalRMessage signalRBroadcaster, - IMovieService moviesService, + IMovieService movieService, + IAddMovieService addMovieService, IMapCoversToLocal coverMapper, RootFolderValidator rootFolderValidator, MappedNetworkDriveValidator mappedNetworkDriveValidator, @@ -43,7 +45,8 @@ public MovieModule(IBroadcastSignalRMessage signalRBroadcaster, ProfileExistsValidator profileExistsValidator) : base(signalRBroadcaster) { - _moviesService = moviesService; + _movieService = movieService; + _addMovieService = addMovieService; _coverMapper = coverMapper; @@ -80,7 +83,7 @@ public MovieModule(IBroadcastSignalRMessage signalRBroadcaster, private MovieResource GetMovie(int id) { - var movies = _moviesService.GetMovie(id); + var movies = _movieService.GetMovie(id); return MapToResource(movies); } @@ -99,7 +102,7 @@ protected MovieResource MapToResource(Movie movies) private List AllMovie() { - var moviesResources = _moviesService.GetAllMovies().ToResource(); + var moviesResources = _movieService.GetAllMovies().ToResource(); MapCoversToLocal(moviesResources.ToArray()); @@ -110,14 +113,14 @@ private int AddMovie(MovieResource moviesResource) { var model = moviesResource.ToModel(); - return _moviesService.AddMovie(model).Id; + return _addMovieService.AddMovie(model).Id; } private void UpdateMovie(MovieResource moviesResource) { - var model = moviesResource.ToModel(_moviesService.GetMovie(moviesResource.Id)); + var model = moviesResource.ToModel(_movieService.GetMovie(moviesResource.Id)); - _moviesService.UpdateMovie(model); + _movieService.UpdateMovie(model); BroadcastResourceChange(ModelAction.Updated, moviesResource); } @@ -139,7 +142,7 @@ private void DeleteMovie(int id) addExclusion = Convert.ToBoolean(addExclusionQuery.Value); } - _moviesService.DeleteMovie(id, deleteFiles, addExclusion); + _movieService.DeleteMovie(id, deleteFiles, addExclusion); } private void MapCoversToLocal(params MovieResource[] movies) diff --git a/src/NzbDrone.Api/NetImport/ListImportModule.cs b/src/NzbDrone.Api/NetImport/ListImportModule.cs index d2c7ea0d4..86d3aea24 100644 --- a/src/NzbDrone.Api/NetImport/ListImportModule.cs +++ b/src/NzbDrone.Api/NetImport/ListImportModule.cs @@ -11,13 +11,13 @@ namespace NzbDrone.Api.NetImport { public class ListImportModule : NzbDroneApiModule { - private readonly IMovieService _movieService; + private readonly IAddMovieService _addMovieService; private readonly ISearchForNewMovie _movieSearch; - public ListImportModule(IMovieService movieService, ISearchForNewMovie movieSearch) + public ListImportModule(IAddMovieService addMovieService, ISearchForNewMovie movieSearch) : base("/movie/import") { - _movieService = movieService; + _addMovieService = addMovieService; _movieSearch = movieSearch; Put("/", movie => SaveAll()); } @@ -28,7 +28,7 @@ private object SaveAll() var movies = resources.Select(movieResource => _movieSearch.MapMovieToTmdbMovie(movieResource.ToModel())).Where(m => m != null).DistinctBy(m => m.TmdbId).ToList(); - return ResponseWithCode(_movieService.AddMovies(movies).ToResource(), HttpStatusCode.Accepted); + return ResponseWithCode(_addMovieService.AddMovies(movies).ToResource(), HttpStatusCode.Accepted); } } } diff --git a/src/NzbDrone.Core.Test/BulkImport/AddMultiMoviesFixture.cs b/src/NzbDrone.Core.Test/BulkImport/AddMultiMoviesFixture.cs deleted file mode 100644 index 0d33b7a45..000000000 --- a/src/NzbDrone.Core.Test/BulkImport/AddMultiMoviesFixture.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System.Collections.Generic; -using FizzWare.NBuilder; -using FluentAssertions; -using Moq; -using NUnit.Framework; -using NzbDrone.Core.Movies; -using NzbDrone.Core.Organizer; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.BulkImport -{ - [TestFixture] - public class AddMultiMoviesFixture : CoreTest - { - private List _fakeMovies; - - [SetUp] - public void Setup() - { - _fakeMovies = Builder.CreateListOfSize(3).BuildList(); - _fakeMovies.ForEach(m => - { - m.Path = null; - m.RootFolderPath = @"C:\Test\TV"; - }); - } - - [Test] - public void movies_added_event_should_have_proper_path() - { - Mocker.GetMock() - .Setup(s => s.GetMovieFolder(It.IsAny(), null)) - .Returns((Movie m, NamingConfig n) => m.Title); - - Mocker.GetMock().Setup(s => s.FindByTmdbId(It.IsAny>())) - .Returns(new List()); - - var movies = Subject.AddMovies(_fakeMovies); - - foreach (Movie movie in movies) - { - movie.Path.Should().NotBeNullOrEmpty(); - } - - // Subject.GetAllMovies().Should().HaveCount(3); - } - - [Test] - public void movies_added_should_ignore_already_added() - { - Mocker.GetMock() - .Setup(s => s.GetMovieFolder(It.IsAny(), null)) - .Returns((Movie m, NamingConfig n) => m.Title); - - Mocker.GetMock().Setup(s => s.FindByTmdbId(It.IsAny>())) - .Returns(new List { _fakeMovies[0] }); - - var movies = Subject.AddMovies(_fakeMovies); - - Mocker.GetMock().Verify(v => v.InsertMany(It.Is>(l => l.Count == 2))); - } - - [Test] - public void movies_added_should_ignore_duplicates() - { - Mocker.GetMock() - .Setup(s => s.GetMovieFolder(It.IsAny(), null)) - .Returns((Movie m, NamingConfig n) => m.Title); - - Mocker.GetMock().Setup(s => s.FindByTmdbId(It.IsAny>())) - .Returns(new List()); - - _fakeMovies[2].TmdbId = _fakeMovies[0].TmdbId; - - var movies = Subject.AddMovies(_fakeMovies); - - Mocker.GetMock().Verify(v => v.InsertMany(It.Is>(l => l.Count == 2))); - } - } -} diff --git a/src/NzbDrone.Core.Test/MovieTests/AddMovieFixture.cs b/src/NzbDrone.Core.Test/MovieTests/AddMovieFixture.cs new file mode 100644 index 000000000..80655b757 --- /dev/null +++ b/src/NzbDrone.Core.Test/MovieTests/AddMovieFixture.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.IO; +using FizzWare.NBuilder; +using FluentAssertions; +using FluentValidation; +using FluentValidation.Results; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Exceptions; +using NzbDrone.Core.MetadataSource; +using NzbDrone.Core.Movies; +using NzbDrone.Core.Movies.Credits; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.MovieTests +{ + [TestFixture] + public class AddMovieFixture : CoreTest + { + private Movie _fakeMovie; + + [SetUp] + public void Setup() + { + _fakeMovie = Builder + .CreateNew() + .With(s => s.Path = null) + .Build(); + } + + private void GivenValidMovie(int tmdbId) + { + Mocker.GetMock() + .Setup(s => s.GetMovieInfo(tmdbId, true)) + .Returns(new Tuple>(_fakeMovie, new List())); + } + + private void GivenValidPath() + { + Mocker.GetMock() + .Setup(s => s.GetMovieFolder(It.IsAny(), null)) + .Returns((c, n) => c.Title); + + Mocker.GetMock() + .Setup(s => s.Validate(It.IsAny())) + .Returns(new ValidationResult()); + } + + [Test] + public void should_be_able_to_add_a_movie_without_passing_in_title() + { + var newMovie = new Movie + { + TmdbId = 1, + RootFolderPath = @"C:\Test\Movies" + }; + + GivenValidMovie(newMovie.TmdbId); + GivenValidPath(); + + var series = Subject.AddMovie(newMovie); + + series.Title.Should().Be(_fakeMovie.Title); + } + + [Test] + public void should_have_proper_path() + { + var newMovie = new Movie + { + TmdbId = 1, + RootFolderPath = @"C:\Test\Movies" + }; + + GivenValidMovie(newMovie.TmdbId); + GivenValidPath(); + + var series = Subject.AddMovie(newMovie); + + series.Path.Should().Be(Path.Combine(newMovie.RootFolderPath, _fakeMovie.Title)); + } + + [Test] + public void should_throw_if_movie_validation_fails() + { + var newMovie = new Movie + { + TmdbId = 1, + Path = @"C:\Test\Movie\Title1" + }; + + GivenValidMovie(newMovie.TmdbId); + + Mocker.GetMock() + .Setup(s => s.Validate(It.IsAny())) + .Returns(new ValidationResult(new List + { + new ValidationFailure("Path", "Test validation failure") + })); + + Assert.Throws(() => Subject.AddMovie(newMovie)); + } + + [Test] + public void should_throw_if_movie_cannot_be_found() + { + var newMovie = new Movie + { + TmdbId = 1, + Path = @"C:\Test\Movie\Title1" + }; + + Mocker.GetMock() + .Setup(s => s.GetMovieInfo(newMovie.TmdbId, true)) + .Throws(new MovieNotFoundException("Movie Not Found")); + + Mocker.GetMock() + .Setup(s => s.Validate(It.IsAny())) + .Returns(new ValidationResult(new List + { + new ValidationFailure("Path", "Test validation failure") + })); + + Assert.Throws(() => Subject.AddMovie(newMovie)); + + ExceptionVerification.ExpectedErrors(1); + } + } +} diff --git a/src/NzbDrone.Core.Test/MovieTests/MovieServiceTests/AddMovieFixture.cs b/src/NzbDrone.Core.Test/MovieTests/MovieServiceTests/AddMovieFixture.cs deleted file mode 100644 index 4a01f5f78..000000000 --- a/src/NzbDrone.Core.Test/MovieTests/MovieServiceTests/AddMovieFixture.cs +++ /dev/null @@ -1,36 +0,0 @@ -using FizzWare.NBuilder; -using FluentAssertions; -using NUnit.Framework; -using NzbDrone.Core.Movies; -using NzbDrone.Core.Organizer; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.MovieTests.MovieServiceTests -{ - [TestFixture] - public class AddMovieFixture : CoreTest - { - private Movie _fakeMovie; - - [SetUp] - public void Setup() - { - _fakeMovie = Builder.CreateNew().Build(); - } - - [Test] - public void movie_added_event_should_have_proper_path() - { - _fakeMovie.Path = null; - _fakeMovie.RootFolderPath = @"C:\Test\Movies"; - - Mocker.GetMock() - .Setup(s => s.GetMovieFolder(_fakeMovie, null)) - .Returns(_fakeMovie.Title); - - var series = Subject.AddMovie(_fakeMovie); - - series.Path.Should().NotBeNull(); - } - } -} diff --git a/src/NzbDrone.Core.Test/MovieTests/MovieTitleSlugValidatorFixture.cs b/src/NzbDrone.Core.Test/MovieTests/MovieTitleSlugValidatorFixture.cs new file mode 100644 index 000000000..2dbfbb52d --- /dev/null +++ b/src/NzbDrone.Core.Test/MovieTests/MovieTitleSlugValidatorFixture.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Movies; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.MovieTests +{ + [TestFixture] + public class MovieTitleSlugValidatorFixture : CoreTest + { + private List _movies; + private TestValidator _validator; + + [SetUp] + public void Setup() + { + _movies = Builder.CreateListOfSize(1) + .Build() + .ToList(); + + _validator = new TestValidator + { + v => v.RuleFor(s => s.TitleSlug).SetValidator(Subject) + }; + + Mocker.GetMock() + .Setup(s => s.GetAllMovies()) + .Returns(_movies); + } + + [Test] + public void should_not_be_valid_if_there_is_an_existing_movie_with_the_same_title_slug() + { + var movie = Builder.CreateNew() + .With(s => s.Id = 100) + .With(s => s.TitleSlug = _movies.First().TitleSlug) + .Build(); + + _validator.Validate(movie).IsValid.Should().BeFalse(); + } + + [Test] + public void should_be_valid_if_there_is_not_an_existing_movie_with_the_same_title_slug() + { + var movie = Builder.CreateNew() + .With(s => s.TitleSlug = "MyTitleSlug") + .Build(); + + _validator.Validate(movie).IsValid.Should().BeTrue(); + } + + [Test] + public void should_be_valid_if_there_is_an_existing_movie_with_a_null_title_slug() + { + _movies.First().TitleSlug = null; + + var movie = Builder.CreateNew() + .With(s => s.TitleSlug = "MyTitleSlug") + .Build(); + + _validator.Validate(movie).IsValid.Should().BeTrue(); + } + + [Test] + public void should_be_valid_when_updating_an_existing_movie() + { + _validator.Validate(_movies.First().JsonClone()).IsValid.Should().BeTrue(); + } + } +} diff --git a/src/NzbDrone.Core.Test/NetImport/NetImportSearchServiceFixture.cs b/src/NzbDrone.Core.Test/NetImport/NetImportSearchServiceFixture.cs index ae605ac1d..441bf6e5f 100644 --- a/src/NzbDrone.Core.Test/NetImport/NetImportSearchServiceFixture.cs +++ b/src/NzbDrone.Core.Test/NetImport/NetImportSearchServiceFixture.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using FizzWare.NBuilder; using Moq; @@ -279,7 +279,7 @@ public void should_add_new_movies_from_single_list_to_library() Subject.Execute(_command); - Mocker.GetMock() + Mocker.GetMock() .Verify(v => v.AddMovies(It.Is>(s => s.Count == 5)), Times.Once()); } @@ -293,7 +293,7 @@ public void should_add_new_movies_from_multiple_list_to_library() Subject.Execute(_command); - Mocker.GetMock() + Mocker.GetMock() .Verify(v => v.AddMovies(It.Is>(s => s.Count == 8)), Times.Once()); } @@ -307,7 +307,7 @@ public void should_add_new_movies_from_enabled_lists_to_library() Subject.Execute(_command); - Mocker.GetMock() + Mocker.GetMock() .Verify(v => v.AddMovies(It.Is>(s => s.Count == 5)), Times.Once()); } @@ -323,7 +323,7 @@ public void should_not_add_duplicate_movies_from_seperate_lists() Subject.Execute(_command); - Mocker.GetMock() + Mocker.GetMock() .Verify(v => v.AddMovies(It.Is>(s => s.Count == 7)), Times.Once()); } @@ -341,7 +341,7 @@ public void should_not_add_movie_from_on_exclusion_list() Subject.Execute(_command); - Mocker.GetMock() + Mocker.GetMock() .Verify(v => v.AddMovies(It.Is>(s => s.Count == 7 && !s.Any(m => m.TmdbId == _moviesList2[0].TmdbId))), Times.Once()); } @@ -359,7 +359,7 @@ public void should_not_add_movie_that_exists_in_library() Subject.Execute(_command); - Mocker.GetMock() + Mocker.GetMock() .Verify(v => v.AddMovies(It.Is>(s => s.Count == 7 && !s.Any(m => m.TmdbId == _moviesList2[0].TmdbId))), Times.Once()); } diff --git a/src/NzbDrone.Core/Movies/AddMovieService.cs b/src/NzbDrone.Core/Movies/AddMovieService.cs new file mode 100644 index 000000000..70f0aa3b0 --- /dev/null +++ b/src/NzbDrone.Core/Movies/AddMovieService.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.IO; +using FluentValidation; +using FluentValidation.Results; +using NLog; +using NzbDrone.Common.EnsureThat; +using NzbDrone.Core.Exceptions; +using NzbDrone.Core.MetadataSource; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Parser; + +namespace NzbDrone.Core.Movies +{ + public interface IAddMovieService + { + Movie AddMovie(Movie newMovie); + List AddMovies(List newMovies); + } + + public class AddMovieService : IAddMovieService + { + private readonly IMovieService _movieService; + private readonly IProvideMovieInfo _movieInfo; + private readonly IBuildFileNames _fileNameBuilder; + private readonly IAddMovieValidator _addMovieValidator; + private readonly Logger _logger; + + public AddMovieService(IMovieService movieService, + IProvideMovieInfo movieInfo, + IBuildFileNames fileNameBuilder, + IAddMovieValidator addMovieValidator, + Logger logger) + { + _movieService = movieService; + _movieInfo = movieInfo; + _fileNameBuilder = fileNameBuilder; + _addMovieValidator = addMovieValidator; + _logger = logger; + } + + public Movie AddMovie(Movie newMovie) + { + Ensure.That(newMovie, () => newMovie).IsNotNull(); + + newMovie = AddSkyhookData(newMovie); + newMovie = SetPropertiesAndValidate(newMovie); + + _logger.Info("Adding Movie {0} Path: [{1}]", newMovie, newMovie.Path); + _movieService.AddMovie(newMovie); + + return newMovie; + } + + public List AddMovies(List newMovies) + { + var added = DateTime.UtcNow; + var moviesToAdd = new List(); + + foreach (var m in newMovies) + { + // TODO: Verify if adding skyhook data will be slow + var movie = AddSkyhookData(m); + movie = SetPropertiesAndValidate(movie); + movie.Added = added; + moviesToAdd.Add(movie); + } + + return _movieService.AddMovies(moviesToAdd); + } + + private Movie AddSkyhookData(Movie newMovie) + { + Movie movie; + + try + { + movie = _movieInfo.GetMovieInfo(newMovie.TmdbId, true).Item1; + } + catch (MovieNotFoundException) + { + _logger.Error("TmdbId {1} was not found, it may have been removed from TMDb.", newMovie.TmdbId); + + throw new ValidationException(new List + { + new ValidationFailure("TmdbId", "A movie with this ID was not found", newMovie.TmdbId) + }); + } + + movie.ApplyChanges(newMovie); + + return movie; + } + + private Movie SetPropertiesAndValidate(Movie newMovie) + { + if (string.IsNullOrWhiteSpace(newMovie.Path)) + { + var folderName = _fileNameBuilder.GetMovieFolder(newMovie); + newMovie.Path = Path.Combine(newMovie.RootFolderPath, folderName); + } + + newMovie.CleanTitle = newMovie.Title.CleanSeriesTitle(); + newMovie.SortTitle = MovieTitleNormalizer.Normalize(newMovie.Title, newMovie.TmdbId); + newMovie.Added = DateTime.UtcNow; + + var validationResult = _addMovieValidator.Validate(newMovie); + + if (!validationResult.IsValid) + { + throw new ValidationException(validationResult.Errors); + } + + return newMovie; + } + } +} diff --git a/src/NzbDrone.Core/Movies/AddMovieValidator.cs b/src/NzbDrone.Core/Movies/AddMovieValidator.cs new file mode 100644 index 000000000..a143a7446 --- /dev/null +++ b/src/NzbDrone.Core/Movies/AddMovieValidator.cs @@ -0,0 +1,28 @@ +using FluentValidation; +using FluentValidation.Results; +using NzbDrone.Core.Validation.Paths; + +namespace NzbDrone.Core.Movies +{ + public interface IAddMovieValidator + { + ValidationResult Validate(Movie instance); + } + + public class AddMovieValidator : AbstractValidator, IAddMovieValidator + { + public AddMovieValidator(RootFolderValidator rootFolderValidator, + MoviePathValidator moviePathValidator, + MovieAncestorValidator movieAncestorValidator, + MovieTitleSlugValidator movieTitleSlugValidator) + { + RuleFor(c => c.Path).Cascade(CascadeMode.StopOnFirstFailure) + .IsValidPath() + .SetValidator(rootFolderValidator) + .SetValidator(moviePathValidator) + .SetValidator(movieAncestorValidator); + + RuleFor(c => c.TitleSlug).SetValidator(movieTitleSlugValidator); + } + } +} diff --git a/src/NzbDrone.Core/Movies/MovieService.cs b/src/NzbDrone.Core/Movies/MovieService.cs index 0ab43ba3f..f92b48ce7 100644 --- a/src/NzbDrone.Core/Movies/MovieService.cs +++ b/src/NzbDrone.Core/Movies/MovieService.cs @@ -94,20 +94,6 @@ public PagingSpec Paged(PagingSpec pagingSpec) public Movie AddMovie(Movie newMovie) { - Ensure.That(newMovie, () => newMovie).IsNotNull(); - - if (string.IsNullOrWhiteSpace(newMovie.Path)) - { - var folderName = _fileNameBuilder.GetMovieFolder(newMovie); - newMovie.Path = Path.Combine(newMovie.RootFolderPath, folderName); - } - - _logger.Info("Adding Movie {0} Path: [{1}]", newMovie, newMovie.Path); - - newMovie.CleanTitle = newMovie.Title.CleanSeriesTitle(); - newMovie.SortTitle = MovieTitleNormalizer.Normalize(newMovie.Title, newMovie.TmdbId); - newMovie.Added = DateTime.UtcNow; - _movieRepository.Insert(newMovie); _eventAggregator.PublishEvent(new MovieAddedEvent(GetMovie(newMovie.Id))); @@ -116,33 +102,7 @@ public Movie AddMovie(Movie newMovie) public List AddMovies(List newMovies) { - newMovies.ForEach(m => Ensure.That(m, () => m).IsNotNull()); - - newMovies.ForEach(m => - { - if (string.IsNullOrWhiteSpace(m.Path)) - { - var folderName = _fileNameBuilder.GetMovieFolder(m); - m.Path = Path.Combine(m.RootFolderPath, folderName); - } - - m.CleanTitle = m.Title.CleanSeriesTitle(); - m.SortTitle = MovieTitleNormalizer.Normalize(m.Title, m.TmdbId); - m.Added = DateTime.UtcNow; - }); - - var potentialMovieCount = newMovies.Count; - - newMovies = newMovies.DistinctBy(movie => movie.TmdbId).ToList(); // Ensure we don't add the same movie twice - - var existingMovies = FindByTmdbId(newMovies.Select(x => x.TmdbId).ToList()); - - newMovies = newMovies.ExceptBy(n => n.TmdbId, existingMovies, e => e.TmdbId, EqualityComparer.Default).ToList(); // Ensure we don't add a movie that already exists - _movieRepository.InsertMany(newMovies); - - _logger.Debug("Adding {0} movies, {1} duplicates detected and skipped", newMovies.Count, potentialMovieCount - newMovies.Count); - _eventAggregator.PublishEvent(new MoviesImportedEvent(newMovies.Select(s => s.Id).ToList())); return newMovies; @@ -161,13 +121,13 @@ public Movie FindByTitle(string title, int year) private Movie FindByTitle(string cleanTitle, int? year) { cleanTitle = cleanTitle.ToLowerInvariant(); - string cleanTitleWithRomanNumbers = cleanTitle; - string cleanTitleWithArabicNumbers = cleanTitle; + var cleanTitleWithRomanNumbers = cleanTitle; + var cleanTitleWithArabicNumbers = cleanTitle; - foreach (ArabicRomanNumeral arabicRomanNumeral in RomanNumeralParser.GetArabicRomanNumeralsMapping()) + foreach (var arabicRomanNumeral in RomanNumeralParser.GetArabicRomanNumeralsMapping()) { - string arabicNumber = arabicRomanNumeral.ArabicNumeralAsString; - string romanNumber = arabicRomanNumeral.RomanNumeral; + var arabicNumber = arabicRomanNumeral.ArabicNumeralAsString; + var romanNumber = arabicRomanNumeral.RomanNumeral; cleanTitleWithRomanNumbers = cleanTitleWithRomanNumbers.Replace(arabicNumber, romanNumber); cleanTitleWithArabicNumbers = cleanTitleWithArabicNumbers.Replace(romanNumber, arabicNumber); } diff --git a/src/NzbDrone.Core/Movies/MovieTitleSlugValidator.cs b/src/NzbDrone.Core/Movies/MovieTitleSlugValidator.cs new file mode 100644 index 000000000..b1c215f74 --- /dev/null +++ b/src/NzbDrone.Core/Movies/MovieTitleSlugValidator.cs @@ -0,0 +1,44 @@ +using System.Linq; +using FluentValidation.Validators; +using NzbDrone.Common.Extensions; + +namespace NzbDrone.Core.Movies +{ + public class MovieTitleSlugValidator : PropertyValidator + { + private readonly IMovieService _movieService; + + public MovieTitleSlugValidator(IMovieService movieService) + : base("Title slug '{slug}' is in use by movie '{movieTitle}'") + { + _movieService = movieService; + } + + protected override bool IsValid(PropertyValidatorContext context) + { + if (context.PropertyValue == null) + { + return true; + } + + dynamic instance = context.ParentContext.InstanceToValidate; + var instanceId = (int)instance.Id; + var slug = context.PropertyValue.ToString(); + + var conflictingMovie = _movieService.GetAllMovies() + .FirstOrDefault(s => s.TitleSlug.IsNotNullOrWhiteSpace() && + s.TitleSlug.Equals(context.PropertyValue.ToString()) && + s.Id != instanceId); + + if (conflictingMovie == null) + { + return true; + } + + context.MessageFormatter.AppendArgument("slug", slug); + context.MessageFormatter.AppendArgument("movieTitle", conflictingMovie.Title); + + return false; + } + } +} diff --git a/src/NzbDrone.Core/NetImport/NetImportSearchService.cs b/src/NzbDrone.Core/NetImport/NetImportSearchService.cs index a4fcdf607..baa995d9b 100644 --- a/src/NzbDrone.Core/NetImport/NetImportSearchService.cs +++ b/src/NzbDrone.Core/NetImport/NetImportSearchService.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using NLog; using NzbDrone.Common.Extensions; @@ -21,12 +21,14 @@ public class NetImportSearchService : IFetchNetImport, IExecute movies) diff --git a/src/Radarr.Api.V3/Movies/MovieImportModule.cs b/src/Radarr.Api.V3/Movies/MovieImportModule.cs index 14b36d419..e060c8694 100644 --- a/src/Radarr.Api.V3/Movies/MovieImportModule.cs +++ b/src/Radarr.Api.V3/Movies/MovieImportModule.cs @@ -8,21 +8,21 @@ namespace Radarr.Api.V3.Movies { public class MovieImportModule : RadarrRestModule { - private readonly IMovieService _movieService; + private readonly IAddMovieService _addMovieService; - public MovieImportModule(IMovieService movieService) + public MovieImportModule(IAddMovieService addMovieService) : base("/movie/import") { - _movieService = movieService; + _addMovieService = addMovieService; Post("/", x => Import()); } private object Import() { var resource = Request.Body.FromJson>(); - var newSeries = resource.ToModel(); + var newMovies = resource.ToModel(); - return _movieService.AddMovies(newSeries).ToResource(); + return _addMovieService.AddMovies(newMovies).ToResource(); } } } diff --git a/src/Radarr.Api.V3/Movies/MovieModule.cs b/src/Radarr.Api.V3/Movies/MovieModule.cs index 537eb89ed..f5bcd461e 100644 --- a/src/Radarr.Api.V3/Movies/MovieModule.cs +++ b/src/Radarr.Api.V3/Movies/MovieModule.cs @@ -29,13 +29,15 @@ public class MovieModule : RadarrRestModuleWithSignalR, IHandle, IHandle { - protected readonly IMovieService _moviesService; + private readonly IMovieService _moviesService; + private readonly IAddMovieService _addMovieService; private readonly IMapCoversToLocal _coverMapper; private readonly IManageCommandQueue _commandQueueManager; private readonly IUpgradableSpecification _qualityUpgradableSpecification; public MovieModule(IBroadcastSignalRMessage signalRBroadcaster, IMovieService moviesService, + IAddMovieService addMovieService, IMapCoversToLocal coverMapper, IManageCommandQueue commandQueueManager, IUpgradableSpecification qualityUpgradableSpecification, @@ -50,6 +52,7 @@ public MovieModule(IBroadcastSignalRMessage signalRBroadcaster, : base(signalRBroadcaster) { _moviesService = moviesService; + _addMovieService = addMovieService; _qualityUpgradableSpecification = qualityUpgradableSpecification; _coverMapper = coverMapper; _commandQueueManager = commandQueueManager; @@ -126,7 +129,7 @@ protected MovieResource MapToResource(Movie movies) private int AddMovie(MovieResource moviesResource) { - var movie = _moviesService.AddMovie(moviesResource.ToModel()); + var movie = _addMovieService.AddMovie(moviesResource.ToModel()); return movie.Id; } diff --git a/src/Radarr.Api.V3/NetImport/ListImportModule.cs b/src/Radarr.Api.V3/NetImport/ListImportModule.cs deleted file mode 100644 index 6dad28771..000000000 --- a/src/Radarr.Api.V3/NetImport/ListImportModule.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Nancy; -using Nancy.Extensions; -using NzbDrone.Core.MetadataSource; -using NzbDrone.Core.Movies; -using Radarr.Api.V3.Movies; -using Radarr.Http.Extensions; - -namespace Radarr.Api.V3.NetImport -{ - public class ListImportModule : RadarrV3Module - { - private readonly IMovieService _movieService; - private readonly ISearchForNewMovie _movieSearch; - - public ListImportModule(IMovieService movieService, ISearchForNewMovie movieSearch) - : base("/movie/import") - { - _movieService = movieService; - _movieSearch = movieSearch; - Put("/", movie => SaveAll()); - } - - private object SaveAll() - { - var resources = Request.Body.FromJson>(); - - var movies = resources.Select(movieResource => _movieSearch.MapMovieToTmdbMovie(movieResource.ToModel())).Where(m => m != null).DistinctBy(m => m.TmdbId).ToList(); - - return ResponseWithCode(_movieService.AddMovies(movies).ToResource(), HttpStatusCode.Accepted); - } - } -}