Radarr/src/Radarr.Api.V3/Movies/MovieController.cs

335 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Languages;
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.Movies;
using NzbDrone.Core.Movies.Commands;
using NzbDrone.Core.Movies.Events;
using NzbDrone.Core.Movies.Translations;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Validation;
using NzbDrone.Core.Validation.Paths;
using NzbDrone.SignalR;
using Radarr.Http;
using Radarr.Http.REST;
using Radarr.Http.REST.Attributes;
namespace Radarr.Api.V3.Movies
{
[V3ApiController]
public class MovieController : RestControllerWithSignalR<MovieResource, Movie>,
IHandle<MovieFileImportedEvent>,
IHandle<MovieFileDeletedEvent>,
IHandle<MovieUpdatedEvent>,
IHandle<MovieEditedEvent>,
IHandle<MoviesDeletedEvent>,
IHandle<MovieRenamedEvent>,
IHandle<MediaCoversUpdatedEvent>
{
private readonly IMovieService _moviesService;
private readonly IMovieTranslationService _movieTranslationService;
private readonly IAddMovieService _addMovieService;
private readonly IMapCoversToLocal _coverMapper;
private readonly IManageCommandQueue _commandQueueManager;
private readonly IRootFolderService _rootFolderService;
private readonly IUpgradableSpecification _qualityUpgradableSpecification;
private readonly IConfigService _configService;
private readonly Logger _logger;
public MovieController(IBroadcastSignalRMessage signalRBroadcaster,
IMovieService moviesService,
IMovieTranslationService movieTranslationService,
IAddMovieService addMovieService,
IMapCoversToLocal coverMapper,
IManageCommandQueue commandQueueManager,
IRootFolderService rootFolderService,
IUpgradableSpecification qualityUpgradableSpecification,
IConfigService configService,
RootFolderValidator rootFolderValidator,
MappedNetworkDriveValidator mappedNetworkDriveValidator,
MoviePathValidator moviesPathValidator,
MovieExistsValidator moviesExistsValidator,
MovieAncestorValidator moviesAncestorValidator,
RecycleBinValidator recycleBinValidator,
SystemFolderValidator systemFolderValidator,
QualityProfileExistsValidator qualityProfileExistsValidator,
MovieFolderAsRootFolderValidator movieFolderAsRootFolderValidator,
Logger logger)
: base(signalRBroadcaster)
{
_moviesService = moviesService;
_movieTranslationService = movieTranslationService;
_addMovieService = addMovieService;
_qualityUpgradableSpecification = qualityUpgradableSpecification;
_configService = configService;
_coverMapper = coverMapper;
_commandQueueManager = commandQueueManager;
_rootFolderService = rootFolderService;
_logger = logger;
SharedValidator.RuleFor(s => s.QualityProfileId).ValidId();
SharedValidator.RuleFor(s => s.Path)
.Cascade(CascadeMode.Stop)
.IsValidPath()
.SetValidator(rootFolderValidator)
.SetValidator(mappedNetworkDriveValidator)
.SetValidator(moviesPathValidator)
.SetValidator(moviesAncestorValidator)
.SetValidator(recycleBinValidator)
.SetValidator(systemFolderValidator)
.When(s => !s.Path.IsNullOrWhiteSpace());
SharedValidator.RuleFor(s => s.QualityProfileId).SetValidator(qualityProfileExistsValidator);
PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace());
PostValidator.RuleFor(s => s.RootFolderPath)
.IsValidPath()
.SetValidator(movieFolderAsRootFolderValidator)
.When(s => s.Path.IsNullOrWhiteSpace());
PostValidator.RuleFor(s => s.Title).NotEmpty().When(s => s.TmdbId <= 0);
PostValidator.RuleFor(s => s.TmdbId).NotNull().NotEmpty().SetValidator(moviesExistsValidator);
PutValidator.RuleFor(s => s.Path).IsValidPath();
}
[HttpGet]
public List<MovieResource> AllMovie(int? tmdbId, bool excludeLocalCovers = false)
{
var moviesResources = new List<MovieResource>();
Dictionary<string, FileInfo> coverFileInfos = null;
if (tmdbId.HasValue)
{
var movie = _moviesService.FindByTmdbId(tmdbId.Value);
if (movie != null)
{
moviesResources.AddIfNotNull(MapToResource(movie));
}
}
else
{
var configLanguage = (Language)_configService.MovieInfoLanguage;
var availDelay = _configService.AvailabilityDelay;
var movieTask = Task.Run(() => _moviesService.GetAllMovies());
var translations = _movieTranslationService
.GetAllTranslationsForLanguage(configLanguage);
var tdict = translations.ToDictionary(x => x.MovieMetadataId);
if (!excludeLocalCovers)
{
coverFileInfos = _coverMapper.GetCoverFileInfos();
}
var movies = movieTask.GetAwaiter().GetResult();
moviesResources = new List<MovieResource>(movies.Count);
foreach (var movie in movies)
{
var translation = GetTranslationFromDict(tdict, movie.MovieMetadata, configLanguage);
moviesResources.Add(movie.ToResource(availDelay, translation, _qualityUpgradableSpecification));
}
if (!excludeLocalCovers)
{
MapCoversToLocal(moviesResources, coverFileInfos);
}
var rootFolders = _rootFolderService.All();
moviesResources.ForEach(m => m.RootFolderPath = _rootFolderService.GetBestRootFolderPath(m.Path, rootFolders));
}
return moviesResources;
}
protected override MovieResource GetResourceById(int id)
{
var movie = _moviesService.GetMovie(id);
return MapToResource(movie);
}
protected MovieResource MapToResource(Movie movie)
{
if (movie == null)
{
return null;
}
var availDelay = _configService.AvailabilityDelay;
var translations = _movieTranslationService.GetAllTranslationsForMovieMetadata(movie.MovieMetadataId);
var translation = GetMovieTranslation(translations, movie.MovieMetadata, (Language)_configService.MovieInfoLanguage);
var resource = movie.ToResource(availDelay, translation, _qualityUpgradableSpecification);
MapCoversToLocal(resource);
resource.RootFolderPath = _rootFolderService.GetBestRootFolderPath(resource.Path);
return resource;
}
private MovieTranslation GetMovieTranslation(List<MovieTranslation> translations, MovieMetadata movie, Language configLanguage)
{
if (configLanguage == Language.Original)
{
return new MovieTranslation
{
Title = movie.OriginalTitle,
Overview = movie.Overview
};
}
return translations.FirstOrDefault(t => t.Language == configLanguage && t.MovieMetadataId == movie.Id);
}
private MovieTranslation GetTranslationFromDict(Dictionary<int, MovieTranslation> translations, MovieMetadata movie, Language configLanguage)
{
if (configLanguage == Language.Original)
{
return new MovieTranslation
{
Title = movie.OriginalTitle,
Overview = movie.Overview
};
}
if (!translations.TryGetValue(movie.Id, out var translation))
{
translation = new MovieTranslation
{
Title = movie.Title,
Language = Language.English
};
}
return translation;
}
[RestPostById]
public ActionResult<MovieResource> AddMovie(MovieResource moviesResource)
{
var movie = _addMovieService.AddMovie(moviesResource.ToModel());
return Created(movie.Id);
}
[RestPutById]
public ActionResult<MovieResource> UpdateMovie(MovieResource moviesResource, bool moveFiles = false)
{
var movie = _moviesService.GetMovie(moviesResource.Id);
if (moveFiles)
{
var sourcePath = movie.Path;
var destinationPath = moviesResource.Path;
_commandQueueManager.Push(new MoveMovieCommand
{
MovieId = movie.Id,
SourcePath = sourcePath,
DestinationPath = destinationPath,
Trigger = CommandTrigger.Manual
});
}
var model = moviesResource.ToModel(movie);
var updatedMovie = _moviesService.UpdateMovie(model);
BroadcastResourceChange(ModelAction.Updated, MapToResource(updatedMovie));
return Accepted(moviesResource.Id);
}
[RestDeleteById]
public void DeleteMovie(int id, bool deleteFiles = false, bool addImportExclusion = false)
{
_moviesService.DeleteMovie(id, deleteFiles, addImportExclusion);
}
private void MapCoversToLocal(MovieResource movie)
{
_coverMapper.ConvertToLocalUrls(movie.Id, movie.Images);
}
private void MapCoversToLocal(IEnumerable<MovieResource> movies, Dictionary<string, FileInfo> coverFileInfos)
{
_coverMapper.ConvertToLocalUrls(movies.Select(x => Tuple.Create(x.Id, x.Images.AsEnumerable())), coverFileInfos);
}
[NonAction]
public void Handle(MovieFileImportedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, message.MovieInfo.Movie.Id);
}
[NonAction]
public void Handle(MovieFileDeletedEvent message)
{
if (message.Reason == DeleteMediaFileReason.Upgrade)
{
return;
}
BroadcastResourceChange(ModelAction.Updated, message.MovieFile.MovieId);
}
[NonAction]
public void Handle(MovieUpdatedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, MapToResource(message.Movie));
}
[NonAction]
public void Handle(MovieEditedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, MapToResource(message.Movie));
}
[NonAction]
public void Handle(MoviesDeletedEvent message)
{
foreach (var movie in message.Movies)
{
BroadcastResourceChange(ModelAction.Deleted, movie.Id);
}
}
[NonAction]
public void Handle(MovieRenamedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, MapToResource(message.Movie));
}
[NonAction]
public void Handle(MediaCoversUpdatedEvent message)
{
if (message.Updated)
{
BroadcastResourceChange(ModelAction.Updated, message.Movie.Id);
}
}
}
}