diff --git a/src/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs b/src/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs index 16a0e1676..a27ababb7 100644 --- a/src/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using FizzWare.NBuilder; using FluentAssertions; @@ -40,15 +41,16 @@ public void should_convert_cover_urls_to_local() new MediaCover.MediaCover { CoverType = MediaCoverTypes.Banner } }; - Mocker.GetMock().Setup(c => c.FileGetLastWrite(It.IsAny())) - .Returns(new DateTime(1234)); + var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Media", "H264_sample.mp4"); + var fileInfo = new FileInfo(path); - Mocker.GetMock().Setup(c => c.FileExists(It.IsAny())) - .Returns(true); + Mocker.GetMock() + .Setup(c => c.GetFileInfo(It.IsAny())) + .Returns(fileInfo); Subject.ConvertToLocalUrls(12, covers); - covers.Single().Url.Should().Be("/MediaCover/12/banner.jpg?lastWrite=1234"); + covers.Single().Url.Should().Be($"/MediaCover/12/banner.jpg?lastWrite={fileInfo.LastWriteTimeUtc.Ticks}"); } [Test] @@ -59,6 +61,13 @@ public void should_convert_media_urls_to_local_without_time_if_file_doesnt_exist new MediaCover.MediaCover { CoverType = MediaCoverTypes.Banner } }; + var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Media", "NonExistant.mp4"); + var fileInfo = new FileInfo(path); + + Mocker.GetMock() + .Setup(c => c.GetFileInfo(It.IsAny())) + .Returns(fileInfo); + Subject.ConvertToLocalUrls(12, covers); covers.Single().Url.Should().Be("/MediaCover/12/banner.jpg"); diff --git a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs index 3128df70e..04389e97a 100644 --- a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs +++ b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net; using System.Threading; using NLog; +using NzbDrone.Common; using NzbDrone.Common.Disk; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; @@ -17,7 +19,8 @@ namespace NzbDrone.Core.MediaCover { public interface IMapCoversToLocal { - void ConvertToLocalUrls(int movieId, IEnumerable covers); + Dictionary GetCoverFileInfos(); + void ConvertToLocalUrls(int movieId, IEnumerable covers, Dictionary fileInfos = null); string GetCoverPath(int movieId, MediaCoverTypes mediaCoverTypes, int? height = null); } @@ -70,7 +73,19 @@ public string GetCoverPath(int movieId, MediaCoverTypes coverTypes, int? height return Path.Combine(GetMovieCoverPath(movieId), coverTypes.ToString().ToLower() + heightSuffix + ".jpg"); } - public void ConvertToLocalUrls(int movieId, IEnumerable covers) + public Dictionary GetCoverFileInfos() + { + if (!_diskProvider.FolderExists(_coverRootFolder)) + { + return new Dictionary(); + } + + return _diskProvider + .GetFileInfos(_coverRootFolder, SearchOption.AllDirectories) + .ToDictionary(x => x.FullName, PathEqualityComparer.Instance); + } + + public void ConvertToLocalUrls(int movieId, IEnumerable covers, Dictionary fileInfos = null) { if (movieId == 0) { @@ -90,9 +105,21 @@ public void ConvertToLocalUrls(int movieId, IEnumerable covers) mediaCover.RemoteUrl = mediaCover.Url; mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/" + movieId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg"; - if (_diskProvider.FileExists(filePath)) + FileInfo file; + var fileExists = false; + if (fileInfos != null) { - var lastWrite = _diskProvider.FileGetLastWrite(filePath); + fileExists = fileInfos.TryGetValue(filePath, out file); + } + else + { + file = _diskProvider.GetFileInfo(filePath); + fileExists = file.Exists; + } + + if (fileExists) + { + var lastWrite = file.LastWriteTimeUtc; mediaCover.Url += "?lastWrite=" + lastWrite.Ticks; } } diff --git a/src/Radarr.Api.V3/Movies/MovieModule.cs b/src/Radarr.Api.V3/Movies/MovieModule.cs index db2742c99..fba523c45 100644 --- a/src/Radarr.Api.V3/Movies/MovieModule.cs +++ b/src/Radarr.Api.V3/Movies/MovieModule.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Threading.Tasks; using FluentValidation; using Nancy; using NzbDrone.Common.Extensions; @@ -42,21 +44,21 @@ public class MovieModule : RadarrRestModuleWithSignalR, private readonly IConfigService _configService; public MovieModule(IBroadcastSignalRMessage signalRBroadcaster, - IMovieService moviesService, - IMovieTranslationService movieTranslationService, - IAddMovieService addMovieService, - IMapCoversToLocal coverMapper, - IManageCommandQueue commandQueueManager, - IUpgradableSpecification qualityUpgradableSpecification, - IConfigService configService, - RootFolderValidator rootFolderValidator, - MappedNetworkDriveValidator mappedNetworkDriveValidator, - MoviePathValidator moviesPathValidator, - MovieExistsValidator moviesExistsValidator, - MovieAncestorValidator moviesAncestorValidator, - SystemFolderValidator systemFolderValidator, - ProfileExistsValidator profileExistsValidator, - MovieFolderAsRootFolderValidator movieFolderAsRootFolderValidator) + IMovieService moviesService, + IMovieTranslationService movieTranslationService, + IAddMovieService addMovieService, + IMapCoversToLocal coverMapper, + IManageCommandQueue commandQueueManager, + IUpgradableSpecification qualityUpgradableSpecification, + IConfigService configService, + RootFolderValidator rootFolderValidator, + MappedNetworkDriveValidator mappedNetworkDriveValidator, + MoviePathValidator moviesPathValidator, + MovieExistsValidator moviesExistsValidator, + MovieAncestorValidator moviesAncestorValidator, + SystemFolderValidator systemFolderValidator, + ProfileExistsValidator profileExistsValidator, + MovieFolderAsRootFolderValidator movieFolderAsRootFolderValidator) : base(signalRBroadcaster) { _moviesService = moviesService; @@ -104,6 +106,8 @@ private List AllMovie() var moviesResources = new List(); var configLanguage = (Language)_configService.MovieInfoLanguage; + Dictionary coverFileInfos = null; + if (tmdbId > 0) { var movie = _moviesService.FindByTmdbId(tmdbId); @@ -117,18 +121,27 @@ private List AllMovie() } else { - var translations = _movieTranslationService.GetAllTranslationsForLanguage(configLanguage); - var movies = _moviesService.GetAllMovies(); + var movieTask = Task.Run(() => _moviesService.GetAllMovies()); + + var translations = _movieTranslationService + .GetAllTranslationsForLanguage(configLanguage) + .ToDictionary(x => x.MovieId); + + coverFileInfos = _coverMapper.GetCoverFileInfos(); + + var movies = movieTask.GetAwaiter().GetResult(); + + moviesResources = new List(movies.Count); foreach (var movie in movies) { - var translation = GetMovieTranslation(translations, movie, configLanguage); - moviesResources.Add(movie.ToResource(_qualityUpgradableSpecification, translation)); + var translation = GetTranslationFromDict(translations, movie, configLanguage); + var resource = movie.ToResource(_qualityUpgradableSpecification, translation); + _coverMapper.ConvertToLocalUrls(resource.Id, resource.Images, coverFileInfos); + moviesResources.Add(resource); } } - MapCoversToLocal(moviesResources.ToArray()); - return moviesResources; } @@ -168,6 +181,21 @@ private MovieTranslation GetMovieTranslation(List translations return translations.FirstOrDefault(t => t.Language == configLanguage && t.MovieId == movie.Id); } + private MovieTranslation GetTranslationFromDict(Dictionary translations, Movie movie, Language configLanguage) + { + if (configLanguage == Language.Original) + { + return new MovieTranslation + { + Title = movie.OriginalTitle, + Overview = movie.Overview + }; + } + + translations.TryGetValue(movie.Id, out var translation); + return translation; + } + private int AddMovie(MovieResource moviesResource) { var movie = _addMovieService.AddMovie(moviesResource.ToModel()); @@ -211,12 +239,9 @@ private void DeleteMovie(int id) _moviesService.DeleteMovie(id, deleteFiles, addExclusion); } - private void MapCoversToLocal(params MovieResource[] movies) + private void MapCoversToLocal(MovieResource movie) { - foreach (var moviesResource in movies) - { - _coverMapper.ConvertToLocalUrls(moviesResource.Id, moviesResource.Images); - } + _coverMapper.ConvertToLocalUrls(movie.Id, movie.Images); } public void Handle(MovieImportedEvent message)