mirror of
https://github.com/Radarr/Radarr.git
synced 2024-11-04 10:02:40 +01:00
parent
9b162f2d5e
commit
1dab0aee6a
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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" />
|
||||
|
132
src/NzbDrone.Core.Test/TvTests/AddSeriesFixture.cs
Normal file
132
src/NzbDrone.Core.Test/TvTests/AddSeriesFixture.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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" />
|
||||
|
99
src/NzbDrone.Core/Tv/AddSeriesService.cs
Normal file
99
src/NzbDrone.Core/Tv/AddSeriesService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
30
src/NzbDrone.Core/Tv/AddSeriesValidator.cs
Normal file
30
src/NzbDrone.Core/Tv/AddSeriesValidator.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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)));
|
||||
|
||||
|
25
src/NzbDrone.Core/Tv/SeriesTitleSlugValidator.cs
Normal file
25
src/NzbDrone.Core/Tv/SeriesTitleSlugValidator.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user