1
0
mirror of https://github.com/Radarr/Radarr.git synced 2024-11-04 10:02:40 +01:00

Fixed: Reduce parameters required to add a new series

Fixes #1403
This commit is contained in:
Mark McDowall 2017-02-27 21:37:33 -08:00 committed by GitHub
parent 9b162f2d5e
commit 1dab0aee6a
12 changed files with 317 additions and 31 deletions

View File

@ -41,7 +41,7 @@ public static List<SeasonResource> ToResource(this IEnumerable<Season> models)
public static List<Season> ToModel(this IEnumerable<SeasonResource> resources)
{
return resources.Select(ToModel).ToList();
return resources?.Select(ToModel).ToList() ?? new List<Season>();
}
}
}

View File

@ -29,12 +29,14 @@ public class SeriesModule : NzbDroneRestModuleWithSignalR<SeriesResource, Core.T
{
private readonly ISeriesService _seriesService;
private readonly IAddSeriesService _addSeriesService;
private readonly ISeriesStatisticsService _seriesStatisticsService;
private readonly ISceneMappingService _sceneMappingService;
private readonly IMapCoversToLocal _coverMapper;
public SeriesModule(IBroadcastSignalRMessage signalRBroadcaster,
ISeriesService seriesService,
IAddSeriesService addSeriesService,
ISeriesStatisticsService seriesStatisticsService,
ISceneMappingService sceneMappingService,
IMapCoversToLocal coverMapper,
@ -48,6 +50,7 @@ ProfileExistsValidator profileExistsValidator
: base(signalRBroadcaster)
{
_seriesService = seriesService;
_addSeriesService = addSeriesService;
_seriesStatisticsService = seriesStatisticsService;
_sceneMappingService = sceneMappingService;
@ -74,7 +77,6 @@ ProfileExistsValidator profileExistsValidator
PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace());
PostValidator.RuleFor(s => s.RootFolderPath).IsValidPath().When(s => s.Path.IsNullOrWhiteSpace());
PostValidator.RuleFor(s => s.Title).NotEmpty();
PostValidator.RuleFor(s => s.TvdbId).GreaterThan(0).SetValidator(seriesExistsValidator);
PutValidator.RuleFor(s => s.Path).IsValidPath();
@ -114,7 +116,7 @@ private int AddSeries(SeriesResource seriesResource)
{
var model = seriesResource.ToModel();
return _seriesService.AddSeries(model).Id;
return _addSeriesService.AddSeries(model).Id;
}
private void UpdateSeries(SeriesResource seriesResource)

View File

@ -207,19 +207,9 @@ public static Core.Tv.Series ToModel(this SeriesResource resource)
public static Core.Tv.Series ToModel(this SeriesResource resource, Core.Tv.Series series)
{
series.TvdbId = resource.TvdbId;
var updatedSeries = resource.ToModel();
series.Seasons = resource.Seasons.ToModel();
series.Path = resource.Path;
series.ProfileId = resource.ProfileId;
series.SeasonFolder = resource.SeasonFolder;
series.Monitored = resource.Monitored;
series.SeriesType = resource.SeriesType;
series.RootFolderPath = resource.RootFolderPath;
series.Tags = resource.Tags;
series.AddOptions = resource.AddOptions;
series.ApplyChanges(updatedSeries);
return series;
}

View File

@ -380,7 +380,7 @@
<Compile Include="TvTests\RefreshSeriesServiceFixture.cs" />
<Compile Include="TvTests\EpisodeMonitoredServiceTests\SetEpisodeMontitoredFixture.cs" />
<Compile Include="TvTests\SeriesRepositoryTests\SeriesRepositoryFixture.cs" />
<Compile Include="TvTests\SeriesServiceTests\AddSeriesFixture.cs" />
<Compile Include="TvTests\AddSeriesFixture.cs" />
<Compile Include="TvTests\SeriesServiceTests\UpdateMultipleSeriesFixture.cs" />
<Compile Include="TvTests\SeriesServiceTests\UpdateSeriesFixture.cs" />
<Compile Include="TvTests\SeriesTitleNormalizerFixture.cs" />

View File

@ -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.Organizer;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv.Events;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.TvTests
{
[TestFixture]
public class AddSeriesFixture : CoreTest<AddSeriesService>
{
private Series _fakeSeries;
[SetUp]
public void Setup()
{
_fakeSeries = Builder<Series>
.CreateNew()
.With(s => s.Path = null)
.Build();
}
private void GivenValidSeries(int tvdbId)
{
Mocker.GetMock<IProvideSeriesInfo>()
.Setup(s => s.GetSeriesInfo(tvdbId))
.Returns(new Tuple<Series, List<Episode>>(_fakeSeries, new List<Episode>()));
}
private void GivenValidPath()
{
Mocker.GetMock<IBuildFileNames>()
.Setup(s => s.GetSeriesFolder(It.IsAny<Series>(), null))
.Returns<Series, NamingConfig>((c, n) => c.Title);
Mocker.GetMock<IAddSeriesValidator>()
.Setup(s => s.Validate(It.IsAny<Series>()))
.Returns(new ValidationResult());
}
[Test]
public void should_be_able_to_add_a_series_without_passing_in_title()
{
var newSeries = new Series
{
TvdbId = 1,
RootFolderPath = @"C:\Test\TV"
};
GivenValidSeries(newSeries.TvdbId);
GivenValidPath();
var series = Subject.AddSeries(newSeries);
series.Title.Should().Be(_fakeSeries.Title);
}
[Test]
public void should_have_proper_path()
{
var newSeries = new Series
{
TvdbId = 1,
RootFolderPath = @"C:\Test\TV"
};
GivenValidSeries(newSeries.TvdbId);
GivenValidPath();
var series = Subject.AddSeries(newSeries);
series.Path.Should().Be(Path.Combine(newSeries.RootFolderPath, _fakeSeries.Title));
}
[Test]
public void should_throw_if_series_validation_fails()
{
var newSeries = new Series
{
TvdbId = 1,
Path = @"C:\Test\TV\Title1"
};
GivenValidSeries(newSeries.TvdbId);
Mocker.GetMock<IAddSeriesValidator>()
.Setup(s => s.Validate(It.IsAny<Series>()))
.Returns(new ValidationResult(new List<ValidationFailure>
{
new ValidationFailure("Path", "Test validation failure")
}));
Assert.Throws<ValidationException>(() => Subject.AddSeries(newSeries));
}
[Test]
public void should_throw_if_series_cannot_be_found()
{
var newSeries = new Series
{
TvdbId = 1,
Path = @"C:\Test\TV\Title1"
};
Mocker.GetMock<IProvideSeriesInfo>()
.Setup(s => s.GetSeriesInfo(newSeries.TvdbId))
.Throws(new SeriesNotFoundException(newSeries.TvdbId));
Mocker.GetMock<IAddSeriesValidator>()
.Setup(s => s.Validate(It.IsAny<Series>()))
.Returns(new ValidationResult(new List<ValidationFailure>
{
new ValidationFailure("Path", "Test validation failure")
}));
Assert.Throws<ValidationException>(() => Subject.AddSeries(newSeries));
ExceptionVerification.ExpectedErrors(1);
}
}
}

View File

@ -157,6 +157,7 @@ private static Series MapSeries(ShowResource show)
series.Actors = show.Actors.Select(MapActors).ToList();
series.Seasons = show.Seasons.Select(MapSeason).ToList();
series.Images = show.Images.Select(MapImage).ToList();
series.Monitored = true;
return series;
}
@ -208,7 +209,8 @@ private static Season MapSeason(SeasonResource seasonResource)
return new Season
{
SeasonNumber = seasonResource.SeasonNumber,
Images = seasonResource.Images.Select(MapImage).ToList()
Images = seasonResource.Images.Select(MapImage).ToList(),
Monitored = seasonResource.SeasonNumber > 0
};
}

View File

@ -1066,6 +1066,8 @@
<Compile Include="TinyTwitter.cs" />
<Compile Include="Tv\Actor.cs" />
<Compile Include="Tv\AddSeriesOptions.cs" />
<Compile Include="Tv\AddSeriesService.cs" />
<Compile Include="Tv\AddSeriesValidator.cs" />
<Compile Include="Tv\Commands\MoveSeriesCommand.cs" />
<Compile Include="Tv\Commands\RefreshSeriesCommand.cs" />
<Compile Include="Tv\Episode.cs" />
@ -1099,6 +1101,7 @@
</Compile>
<Compile Include="Tv\SeriesStatusType.cs" />
<Compile Include="Tv\SeriesTitleNormalizer.cs" />
<Compile Include="Tv\SeriesTitleSlugValidator.cs" />
<Compile Include="Tv\SeriesTypes.cs" />
<Compile Include="Tv\ShouldRefreshSeries.cs" />
<Compile Include="Update\Commands\ApplicationUpdateCommand.cs" />

View File

@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
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.Tv
{
public interface IAddSeriesService
{
Series AddSeries(Series newSeries);
}
public class AddSeriesService : IAddSeriesService
{
private readonly ISeriesService _seriesService;
private readonly IProvideSeriesInfo _seriesInfo;
private readonly IBuildFileNames _fileNameBuilder;
private readonly IAddSeriesValidator _addSeriesValidator;
private readonly Logger _logger;
public AddSeriesService(ISeriesService seriesService,
IProvideSeriesInfo seriesInfo,
IBuildFileNames fileNameBuilder,
IAddSeriesValidator addSeriesValidator,
Logger logger)
{
_seriesService = seriesService;
_seriesInfo = seriesInfo;
_fileNameBuilder = fileNameBuilder;
_addSeriesValidator = addSeriesValidator;
_logger = logger;
}
public Series AddSeries(Series newSeries)
{
Ensure.That(newSeries, () => newSeries).IsNotNull();
newSeries = AddSkyhookData(newSeries);
if (string.IsNullOrWhiteSpace(newSeries.Path))
{
var folderName = _fileNameBuilder.GetSeriesFolder(newSeries);
newSeries.Path = Path.Combine(newSeries.RootFolderPath, folderName);
}
newSeries.CleanTitle = newSeries.Title.CleanSeriesTitle();
newSeries.SortTitle = SeriesTitleNormalizer.Normalize(newSeries.Title, newSeries.TvdbId);
newSeries.Added = DateTime.UtcNow;
var validationResult = _addSeriesValidator.Validate(newSeries);
if (!validationResult.IsValid)
{
throw new ValidationException(validationResult.Errors);
}
_logger.Info("Adding Series {0} Path: [{1}]", newSeries, newSeries.Path);
_seriesService.AddSeries(newSeries);
return newSeries;
}
private Series AddSkyhookData(Series newSeries)
{
Tuple<Series, List<Episode>> tuple;
try
{
tuple = _seriesInfo.GetSeriesInfo(newSeries.TvdbId);
}
catch (SeriesNotFoundException)
{
_logger.Error("tvdbid {1} was not found, it may have been removed from TheTVDB.", newSeries.TvdbId);
throw new ValidationException(new List<ValidationFailure>
{
new ValidationFailure("TvdbId", "A series with this ID was not found", newSeries.TvdbId)
});
}
var series = tuple.Item1;
// If seasons were passed in on the new series use them, otherwise use the seasons from Skyhook
newSeries.Seasons = newSeries.Seasons != null && newSeries.Seasons.Any() ? newSeries.Seasons : series.Seasons;
series.ApplyChanges(newSeries);
return series;
}
}
}

View File

@ -0,0 +1,30 @@
using FluentValidation;
using FluentValidation.Results;
using NzbDrone.Core.Validation.Paths;
namespace NzbDrone.Core.Tv
{
public interface IAddSeriesValidator
{
ValidationResult Validate(Series instance);
}
public class AddSeriesValidator : AbstractValidator<Series>, IAddSeriesValidator
{
public AddSeriesValidator(RootFolderValidator rootFolderValidator,
SeriesPathValidator seriesPathValidator,
DroneFactoryValidator droneFactoryValidator,
SeriesAncestorValidator seriesAncestorValidator,
SeriesTitleSlugValidator seriesTitleSlugValidator)
{
RuleFor(c => c.Path).Cascade(CascadeMode.StopOnFirstFailure)
.IsValidPath()
.SetValidator(rootFolderValidator)
.SetValidator(seriesPathValidator)
.SetValidator(droneFactoryValidator)
.SetValidator(seriesAncestorValidator);
RuleFor(c => c.TitleSlug).SetValidator(seriesTitleSlugValidator);
}
}
}

View File

@ -57,5 +57,22 @@ public override string ToString()
{
return string.Format("[{0}][{1}]", TvdbId, Title.NullSafe());
}
public void ApplyChanges(Series otherSeries)
{
TvdbId = otherSeries.TvdbId;
Seasons = otherSeries.Seasons;
Path = otherSeries.Path;
ProfileId = otherSeries.ProfileId;
SeasonFolder = otherSeries.SeasonFolder;
Monitored = otherSeries.Monitored;
SeriesType = otherSeries.SeriesType;
RootFolderPath = otherSeries.RootFolderPath;
Tags = otherSeries.Tags;
AddOptions = otherSeries.AddOptions;
}
}
}

View File

@ -67,20 +67,6 @@ public List<Series> GetSeries(IEnumerable<int> seriesIds)
public Series AddSeries(Series newSeries)
{
Ensure.That(newSeries, () => newSeries).IsNotNull();
if (string.IsNullOrWhiteSpace(newSeries.Path))
{
var folderName = _fileNameBuilder.GetSeriesFolder(newSeries);
newSeries.Path = Path.Combine(newSeries.RootFolderPath, folderName);
}
_logger.Info("Adding Series {0} Path: [{1}]", newSeries, newSeries.Path);
newSeries.CleanTitle = newSeries.Title.CleanSeriesTitle();
newSeries.SortTitle = SeriesTitleNormalizer.Normalize(newSeries.Title, newSeries.TvdbId);
newSeries.Added = DateTime.UtcNow;
_seriesRepository.Insert(newSeries);
_eventAggregator.PublishEvent(new SeriesAddedEvent(GetSeries(newSeries.Id)));

View File

@ -0,0 +1,25 @@
using FluentValidation.Validators;
namespace NzbDrone.Core.Tv
{
public class SeriesTitleSlugValidator : PropertyValidator
{
private readonly ISeriesService _seriesService;
public SeriesTitleSlugValidator(ISeriesService seriesService)
: base("Title slug is in use by another series with a similar name")
{
_seriesService = seriesService;
}
protected override bool IsValid(PropertyValidatorContext context)
{
if (context.PropertyValue == null) return true;
dynamic instance = context.ParentContext.InstanceToValidate;
var instanceId = (int)instance.Id;
return !_seriesService.GetAllSeries().Exists(s => s.TitleSlug.Equals(context.PropertyValue.ToString()) && s.Id != instanceId);
}
}
}