You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Sonarr/src/Sonarr.Api.V3/Series/SeriesController.cs

316 lines
12 KiB

7 years ago
using System.Collections.Generic;
using System.Linq;
using FluentValidation;
using Microsoft.AspNetCore.Mvc;
7 years ago
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.SeriesStats;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Commands;
using NzbDrone.Core.Tv.Events;
using NzbDrone.Core.Validation;
using NzbDrone.Core.Validation.Paths;
using NzbDrone.SignalR;
using Sonarr.Http;
using Sonarr.Http.Extensions;
using Sonarr.Http.REST;
using Sonarr.Http.REST.Attributes;
7 years ago
namespace Sonarr.Api.V3.Series
{
[V3ApiController]
public class SeriesController : RestControllerWithSignalR<SeriesResource, NzbDrone.Core.Tv.Series>,
3 years ago
IHandle<EpisodeImportedEvent>,
7 years ago
IHandle<EpisodeFileDeletedEvent>,
3 years ago
IHandle<SeriesUpdatedEvent>,
IHandle<SeriesEditedEvent>,
7 years ago
IHandle<SeriesDeletedEvent>,
IHandle<SeriesRenamedEvent>,
IHandle<MediaCoversUpdatedEvent>
{
private readonly ISeriesService _seriesService;
private readonly IAddSeriesService _addSeriesService;
private readonly ISeriesStatisticsService _seriesStatisticsService;
private readonly ISceneMappingService _sceneMappingService;
private readonly IMapCoversToLocal _coverMapper;
private readonly IManageCommandQueue _commandQueueManager;
private readonly IRootFolderService _rootFolderService;
public SeriesController(IBroadcastSignalRMessage signalRBroadcaster,
7 years ago
ISeriesService seriesService,
IAddSeriesService addSeriesService,
ISeriesStatisticsService seriesStatisticsService,
ISceneMappingService sceneMappingService,
IMapCoversToLocal coverMapper,
IManageCommandQueue commandQueueManager,
IRootFolderService rootFolderService,
RootFolderValidator rootFolderValidator,
MappedNetworkDriveValidator mappedNetworkDriveValidator,
SeriesPathValidator seriesPathValidator,
SeriesExistsValidator seriesExistsValidator,
SeriesAncestorValidator seriesAncestorValidator,
SystemFolderValidator systemFolderValidator,
ProfileExistsValidator profileExistsValidator,
LanguageProfileExistsValidator languageProfileExistsValidator,
3 years ago
SeriesFolderAsRootFolderValidator seriesFolderAsRootFolderValidator)
7 years ago
: base(signalRBroadcaster)
{
_seriesService = seriesService;
_addSeriesService = addSeriesService;
_seriesStatisticsService = seriesStatisticsService;
_sceneMappingService = sceneMappingService;
_coverMapper = coverMapper;
_commandQueueManager = commandQueueManager;
_rootFolderService = rootFolderService;
Http.Validation.RuleBuilderExtensions.ValidId(SharedValidator.RuleFor(s => s.QualityProfileId));
SharedValidator.RuleFor(s => s.Path)
.Cascade(CascadeMode.StopOnFirstFailure)
.IsValidPath()
.SetValidator(rootFolderValidator)
.SetValidator(mappedNetworkDriveValidator)
.SetValidator(seriesPathValidator)
.SetValidator(seriesAncestorValidator)
.SetValidator(systemFolderValidator)
.When(s => !s.Path.IsNullOrWhiteSpace());
SharedValidator.RuleFor(s => s.QualityProfileId).SetValidator(profileExistsValidator);
SharedValidator.RuleFor(s => s.LanguageProfileId).SetValidator(languageProfileExistsValidator);
7 years ago
PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace());
PostValidator.RuleFor(s => s.RootFolderPath)
.IsValidPath()
.SetValidator(seriesFolderAsRootFolderValidator)
.When(s => s.Path.IsNullOrWhiteSpace());
7 years ago
PostValidator.RuleFor(s => s.Title).NotEmpty();
PostValidator.RuleFor(s => s.TvdbId).GreaterThan(0).SetValidator(seriesExistsValidator);
PutValidator.RuleFor(s => s.Path).IsValidPath();
}
[HttpGet]
2 years ago
[Produces("application/json")]
public List<SeriesResource> AllSeries(int? tvdbId, bool includeSeasonImages = false)
7 years ago
{
var seriesStats = _seriesStatisticsService.SeriesStatistics();
var seriesResources = new List<SeriesResource>();
if (tvdbId.HasValue)
{
seriesResources.AddIfNotNull(_seriesService.FindByTvdbId(tvdbId.Value).ToResource(includeSeasonImages));
}
else
{
seriesResources.AddRange(_seriesService.GetAllSeries().Select(s => s.ToResource(includeSeasonImages)));
}
7 years ago
MapCoversToLocal(seriesResources.ToArray());
LinkSeriesStatistics(seriesResources, seriesStats);
PopulateAlternateTitles(seriesResources);
seriesResources.ForEach(LinkRootFolderPath);
7 years ago
return seriesResources;
}
protected override SeriesResource GetResourceById(int id)
7 years ago
{
var series = _seriesService.GetSeries(id);
// Parse IncludeImages and use it
return GetSeriesResource(series, false);
7 years ago
}
[RestPostById]
2 years ago
[Consumes("application/json")]
public ActionResult<SeriesResource> AddSeries(SeriesResource seriesResource)
7 years ago
{
var series = _addSeriesService.AddSeries(seriesResource.ToModel());
return Created(series.Id);
7 years ago
}
[RestPutById]
2 years ago
[Consumes("application/json")]
public ActionResult<SeriesResource> UpdateSeries(SeriesResource seriesResource)
7 years ago
{
var moveFiles = Request.GetBooleanQueryParameter("moveFiles");
var series = _seriesService.GetSeries(seriesResource.Id);
if (moveFiles)
{
var sourcePath = series.Path;
var destinationPath = seriesResource.Path;
_commandQueueManager.Push(new MoveSeriesCommand
{
SeriesId = series.Id,
SourcePath = sourcePath,
DestinationPath = destinationPath,
Trigger = CommandTrigger.Manual
});
}
var model = seriesResource.ToModel(series);
_seriesService.UpdateSeries(model);
BroadcastResourceChange(ModelAction.Updated, seriesResource);
return Accepted(seriesResource.Id);
7 years ago
}
[RestDeleteById]
public void DeleteSeries(int id)
7 years ago
{
var deleteFiles = Request.GetBooleanQueryParameter("deleteFiles");
var addImportListExclusion = Request.GetBooleanQueryParameter("addImportListExclusion");
7 years ago
_seriesService.DeleteSeries(id, deleteFiles, addImportListExclusion);
7 years ago
}
private SeriesResource GetSeriesResource(NzbDrone.Core.Tv.Series series, bool includeSeasonImages)
{
3 years ago
if (series == null)
{
return null;
}
7 years ago
var resource = series.ToResource(includeSeasonImages);
MapCoversToLocal(resource);
FetchAndLinkSeriesStatistics(resource);
PopulateAlternateTitles(resource);
LinkRootFolderPath(resource);
return resource;
}
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<SeriesResource> resources, List<SeriesStatistics> seriesStatistics)
{
foreach (var series in resources)
{
var stats = seriesStatistics.SingleOrDefault(ss => ss.SeriesId == series.Id);
3 years ago
if (stats == null)
{
continue;
}
7 years ago
LinkSeriesStatistics(series, stats);
}
}
private void LinkSeriesStatistics(SeriesResource resource, SeriesStatistics seriesStatistics)
{
resource.PreviousAiring = seriesStatistics.PreviousAiring;
resource.NextAiring = seriesStatistics.NextAiring;
resource.Statistics = seriesStatistics.ToResource(resource.Seasons);
if (seriesStatistics.SeasonStatistics != null)
{
foreach (var season in resource.Seasons)
{
season.Statistics = seriesStatistics.SeasonStatistics.SingleOrDefault(s => s.SeasonNumber == season.SeasonNumber).ToResource();
}
}
}
private void PopulateAlternateTitles(List<SeriesResource> resources)
{
foreach (var resource in resources)
{
PopulateAlternateTitles(resource);
}
}
private void PopulateAlternateTitles(SeriesResource resource)
{
var mappings = _sceneMappingService.FindByTvdbId(resource.TvdbId);
3 years ago
if (mappings == null)
{
return;
}
7 years ago
resource.AlternateTitles = mappings.ConvertAll(AlternateTitleResourceMapper.ToResource);
7 years ago
}
private void LinkRootFolderPath(SeriesResource resource)
{
resource.RootFolderPath = _rootFolderService.GetBestRootFolderPath(resource.Path);
}
[NonAction]
7 years ago
public void Handle(EpisodeImportedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, message.ImportedEpisode.SeriesId);
}
[NonAction]
7 years ago
public void Handle(EpisodeFileDeletedEvent message)
{
3 years ago
if (message.Reason == DeleteMediaFileReason.Upgrade)
{
return;
}
7 years ago
BroadcastResourceChange(ModelAction.Updated, message.EpisodeFile.SeriesId);
}
[NonAction]
7 years ago
public void Handle(SeriesUpdatedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, message.Series.Id);
}
[NonAction]
7 years ago
public void Handle(SeriesEditedEvent message)
{
var resource = message.Series.ToResource();
resource.EpisodesChanged = message.EpisodesChanged;
BroadcastResourceChange(ModelAction.Updated, resource);
7 years ago
}
[NonAction]
7 years ago
public void Handle(SeriesDeletedEvent message)
{
BroadcastResourceChange(ModelAction.Deleted, message.Series.ToResource());
}
[NonAction]
7 years ago
public void Handle(SeriesRenamedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, message.Series.Id);
}
[NonAction]
7 years ago
public void Handle(MediaCoversUpdatedEvent message)
{
if (message.Updated)
{
BroadcastResourceChange(ModelAction.Updated, message.Series.Id);
}
7 years ago
}
}
}