using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Mvc; using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Exceptions; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser; using NzbDrone.Core.Tv; using NzbDrone.SignalR; using Sonarr.Api.V3.CustomFormats; using Sonarr.Http; using Sonarr.Http.REST; using Sonarr.Http.REST.Attributes; using BadRequestException = Sonarr.Http.REST.BadRequestException; namespace Sonarr.Api.V3.EpisodeFiles { [V3ApiController] public class EpisodeFileController : RestControllerWithSignalR, IHandle, IHandle { private readonly IMediaFileService _mediaFileService; private readonly IDeleteMediaFiles _mediaFileDeletionService; private readonly ISeriesService _seriesService; private readonly ICustomFormatCalculationService _formatCalculator; private readonly IUpgradableSpecification _upgradableSpecification; public EpisodeFileController(IBroadcastSignalRMessage signalRBroadcaster, IMediaFileService mediaFileService, IDeleteMediaFiles mediaFileDeletionService, ISeriesService seriesService, ICustomFormatCalculationService formatCalculator, IUpgradableSpecification upgradableSpecification) : base(signalRBroadcaster) { _mediaFileService = mediaFileService; _mediaFileDeletionService = mediaFileDeletionService; _seriesService = seriesService; _formatCalculator = formatCalculator; _upgradableSpecification = upgradableSpecification; } protected override EpisodeFileResource GetResourceById(int id) { var episodeFile = _mediaFileService.Get(id); var series = _seriesService.GetSeries(episodeFile.SeriesId); var resource = episodeFile.ToResource(series, _upgradableSpecification); resource.CustomFormats = _formatCalculator.ParseCustomFormat(episodeFile).ToResource(); return resource; } [HttpGet] [Produces("application/json")] public List GetEpisodeFiles(int? seriesId, [FromQuery] List episodeFileIds) { if (!seriesId.HasValue && !episodeFileIds.Any()) { throw new BadRequestException("seriesId or episodeFileIds must be provided"); } if (seriesId.HasValue) { var series = _seriesService.GetSeries(seriesId.Value); var files = _mediaFileService.GetFilesBySeries(seriesId.Value); if (files == null) { return new List(); } var resources = new List(); foreach (var file in files) { var resource = file.ToResource(series, _upgradableSpecification); file.Series = series; resource.CustomFormats = _formatCalculator.ParseCustomFormat(file).ToResource(); resources.Add(resource); } return resources; } else { var episodeFiles = _mediaFileService.Get(episodeFileIds); return episodeFiles.GroupBy(e => e.SeriesId) .SelectMany(f => f.ToList() .ConvertAll(e => e.ToResource(_seriesService.GetSeries(f.Key), _upgradableSpecification))) .ToList(); } } [RestPutById] [Consumes("application/json")] public ActionResult SetQuality(EpisodeFileResource episodeFileResource) { var episodeFile = _mediaFileService.Get(episodeFileResource.Id); episodeFile.Quality = episodeFileResource.Quality; if (episodeFileResource.SceneName != null && SceneChecker.IsSceneTitle(episodeFileResource.SceneName)) { episodeFile.SceneName = episodeFileResource.SceneName; } if (episodeFileResource.ReleaseGroup != null) { episodeFile.ReleaseGroup = episodeFileResource.ReleaseGroup; } _mediaFileService.Update(episodeFile); return Accepted(episodeFile.Id); } [HttpPut("editor")] [Consumes("application/json")] public object SetQuality([FromBody] EpisodeFileListResource resource) { var episodeFiles = _mediaFileService.GetFiles(resource.EpisodeFileIds); foreach (var episodeFile in episodeFiles) { if (resource.Language != null) { episodeFile.Language = resource.Language; } if (resource.Quality != null) { episodeFile.Quality = resource.Quality; } if (resource.SceneName != null && SceneChecker.IsSceneTitle(resource.SceneName)) { episodeFile.SceneName = resource.SceneName; } if (resource.ReleaseGroup != null) { episodeFile.ReleaseGroup = resource.ReleaseGroup; } } _mediaFileService.Update(episodeFiles); var series = _seriesService.GetSeries(episodeFiles.First().SeriesId); return Accepted(episodeFiles.ConvertAll(f => f.ToResource(series, _upgradableSpecification))); } [RestDeleteById] public void DeleteEpisodeFile(int id) { var episodeFile = _mediaFileService.Get(id); if (episodeFile == null) { throw new NzbDroneClientException(global::System.Net.HttpStatusCode.NotFound, "Episode file not found"); } var series = _seriesService.GetSeries(episodeFile.SeriesId); _mediaFileDeletionService.DeleteEpisodeFile(series, episodeFile); } [HttpDelete("bulk")] [Consumes("application/json")] public object DeleteEpisodeFiles([FromBody] EpisodeFileListResource resource) { var episodeFiles = _mediaFileService.GetFiles(resource.EpisodeFileIds); var series = _seriesService.GetSeries(episodeFiles.First().SeriesId); foreach (var episodeFile in episodeFiles) { _mediaFileDeletionService.DeleteEpisodeFile(series, episodeFile); } return new { }; } [HttpPut("bulk")] [Consumes("application/json")] public object SetPropertiesBulk([FromBody] List resources) { var episodeFiles = _mediaFileService.GetFiles(resources.Select(r => r.Id)); foreach (var episodeFile in episodeFiles) { var resourceEpisodeFile = resources.Single(r => r.Id == episodeFile.Id); if (resourceEpisodeFile.Language != null) { episodeFile.Language = resourceEpisodeFile.Language; } if (resourceEpisodeFile.Quality != null) { episodeFile.Quality = resourceEpisodeFile.Quality; } if (resourceEpisodeFile.SceneName != null && SceneChecker.IsSceneTitle(resourceEpisodeFile.SceneName)) { episodeFile.SceneName = resourceEpisodeFile.SceneName; } if (resourceEpisodeFile.ReleaseGroup != null) { episodeFile.ReleaseGroup = resourceEpisodeFile.ReleaseGroup; } } _mediaFileService.Update(episodeFiles); var series = _seriesService.GetSeries(episodeFiles.First().SeriesId); return Accepted(episodeFiles.ConvertAll(f => f.ToResource(series, _upgradableSpecification))); } [NonAction] public void Handle(EpisodeFileAddedEvent message) { BroadcastResourceChange(ModelAction.Updated, message.EpisodeFile.Id); } [NonAction] public void Handle(EpisodeFileDeletedEvent message) { BroadcastResourceChange(ModelAction.Deleted, message.EpisodeFile.Id); } } }