using System; using System.Collections.Generic; using System.Linq; using FluentValidation; using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.MediaCover; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.SeriesStats; using NzbDrone.Core.Tv; using NzbDrone.Api.Validation; using NzbDrone.Api.Mapping; using NzbDrone.Core.Tv.Events; using NzbDrone.Core.Validation.Paths; using NzbDrone.Core.DataAugmentation.Scene; namespace NzbDrone.Api.Series { public class SeriesModule : NzbDroneRestModuleWithSignalR, IHandle, IHandle, IHandle, IHandle, IHandle { private readonly ICommandExecutor _commandExecutor; private readonly ISeriesService _seriesService; private readonly ISeriesStatisticsService _seriesStatisticsService; private readonly ISceneMappingService _sceneMappingService; private readonly IMapCoversToLocal _coverMapper; public SeriesModule(ICommandExecutor commandExecutor, ISeriesService seriesService, ISeriesStatisticsService seriesStatisticsService, ISceneMappingService sceneMappingService, IMapCoversToLocal coverMapper, RootFolderValidator rootFolderValidator, PathExistsValidator pathExistsValidator, SeriesPathValidator seriesPathValidator, SeriesExistsValidator seriesExistsValidator, DroneFactoryValidator droneFactoryValidator ) : base(commandExecutor) { _commandExecutor = commandExecutor; _seriesService = seriesService; _seriesStatisticsService = seriesStatisticsService; _sceneMappingService = sceneMappingService; _coverMapper = coverMapper; GetResourceAll = AllSeries; GetResourceById = GetSeries; CreateResource = AddSeries; UpdateResource = UpdateSeries; DeleteResource = DeleteSeries; SharedValidator.RuleFor(s => s.QualityProfileId).ValidId(); PutValidator.RuleFor(s => s.Path) .Cascade(CascadeMode.StopOnFirstFailure) .IsValidPath() .SetValidator(rootFolderValidator) .SetValidator(seriesPathValidator) .SetValidator(droneFactoryValidator); PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => String.IsNullOrEmpty(s.RootFolderPath)); PostValidator.RuleFor(s => s.RootFolderPath).IsValidPath().When(s => String.IsNullOrEmpty(s.Path)); PostValidator.RuleFor(s => s.Title).NotEmpty(); PostValidator.RuleFor(s => s.TvdbId).GreaterThan(0).SetValidator(seriesExistsValidator); } private void PopulateAlternativeTitles(List resources) { foreach (var resource in resources) { PopulateAlternativeTitles(resource); } } private void PopulateAlternativeTitles(SeriesResource resource) { var mapping = _sceneMappingService.FindByTvdbid(resource.TvdbId); if (mapping == null) return; resource.AlternativeTitles = mapping.Select(x => x.Title).Distinct().ToList(); } private SeriesResource GetSeries(int id) { var series = _seriesService.GetSeries(id); return GetSeriesResource(series); } private SeriesResource GetSeriesResource(Core.Tv.Series series) { if (series == null) return null; var resource = series.InjectTo(); MapCoversToLocal(resource); FetchAndLinkSeriesStatistics(resource); PopulateAlternativeTitles(resource); return resource; } private List AllSeries() { var seriesStats = _seriesStatisticsService.SeriesStatistics(); var seriesResources = ToListResource(_seriesService.GetAllSeries); MapCoversToLocal(seriesResources.ToArray()); LinkSeriesStatistics(seriesResources, seriesStats); PopulateAlternativeTitles(seriesResources); return seriesResources; } private int AddSeries(SeriesResource seriesResource) { return GetNewId(_seriesService.AddSeries, seriesResource); } private void UpdateSeries(SeriesResource seriesResource) { GetNewId(_seriesService.UpdateSeries, seriesResource); BroadcastResourceChange(ModelAction.Updated, seriesResource); } private void DeleteSeries(int id) { var deleteFiles = false; var deleteFilesQuery = Request.Query.deleteFiles; if (deleteFilesQuery.HasValue) { deleteFiles = Convert.ToBoolean(deleteFilesQuery.Value); } _seriesService.DeleteSeries(id, deleteFiles); } private void MapCoversToLocal(params SeriesResource[] series) { foreach (var seriesResource in series) { _coverMapper.ConvertToLocalUrls(seriesResource.Id, seriesResource.Images); } } private void FetchAndLinkSeriesStatistics(SeriesResource resource) { LinkSeriesStatistics(resource, _seriesStatisticsService.SeriesStatistics(resource.Id)); } private void LinkSeriesStatistics(List resources, List seriesStatistics) { foreach (var series in resources) { var stats = seriesStatistics.SingleOrDefault(ss => ss.SeriesId == series.Id); if (stats == null) continue; LinkSeriesStatistics(series, stats); } } private void LinkSeriesStatistics(SeriesResource resource, SeriesStatistics seriesStatistics) { resource.EpisodeCount = seriesStatistics.EpisodeCount; resource.EpisodeFileCount = seriesStatistics.EpisodeFileCount; resource.NextAiring = seriesStatistics.NextAiring; } public void Handle(EpisodeImportedEvent message) { BroadcastResourceChange(ModelAction.Updated, message.ImportedEpisode.SeriesId); } public void Handle(EpisodeFileDeletedEvent message) { if (message.ForUpgrade) return; BroadcastResourceChange(ModelAction.Updated, message.EpisodeFile.SeriesId); } public void Handle(SeriesUpdatedEvent message) { BroadcastResourceChange(ModelAction.Updated, message.Series.Id); } public void Handle(SeriesEditedEvent message) { BroadcastResourceChange(ModelAction.Updated, message.Series.Id); } public void Handle(SeriesDeletedEvent message) { BroadcastResourceChange(ModelAction.Deleted, message.Series.InjectTo()); } } }