diff --git a/frontend/src/Album/AlbumCover.js b/frontend/src/Album/AlbumCover.js index 657cc038a..538fa5db8 100644 --- a/frontend/src/Album/AlbumCover.js +++ b/frontend/src/Album/AlbumCover.js @@ -1,175 +1,25 @@ -import _ from 'lodash'; import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import LazyLoad from 'react-lazyload'; +import React from 'react'; +import ArtistImage from 'Artist/ArtistImage'; const coverPlaceholder = 'data:image/png;base64,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'; -function findCover(images) { - return _.find(images, { coverType: 'cover' }); -} - -function getCoverUrl(cover, size) { - if (cover) { - if (cover.url.contains('lastWrite=') || (/^https?:/).test(cover.url)) { - // Remove protocol - let url = cover.url.replace(/^https?:/, ''); - url = url.replace('cover.jpg', `cover-${size}.jpg`); - - return url; - } - } -} - -class AlbumCover extends Component { - - // - // Lifecycle - - constructor(props, context) { - super(props, context); - - const pixelRatio = Math.floor(window.devicePixelRatio); - - const { - images, - size - } = props; - - const cover = findCover(images); - - this.state = { - pixelRatio, - cover, - coverUrl: getCoverUrl(cover, pixelRatio * size), - isLoaded: false, - hasError: false - }; - } - - componentDidUpdate(prevProps) { - const { - images, - size - } = this.props; - - const { - cover, - pixelRatio - } = this.state; - - const nextCover = findCover(images); - - if (nextCover && (!cover || nextCover.url !== cover.url)) { - this.setState({ - cover: nextCover, - coverUrl: getCoverUrl(nextCover, pixelRatio * size), - hasError: false, - isLoaded: true - }); - } - - // The cover could not be loaded.. - if (!nextCover && (this.props !== prevProps)) { - this.setState({ - cover: undefined, - coverUrl: coverPlaceholder, - hasError: true - }); - } - } - - // - // Listeners - - onError = () => { - this.setState({ hasError: true }); - } - - onLoad = () => { - this.setState({ - isLoaded: true, - hasError: false - }); - } - - // - // Render - - render() { - const { - className, - style, - size, - lazy, - overflow - } = this.props; - - const { - coverUrl, - hasError, - isLoaded - } = this.state; - - if (hasError || !coverUrl) { - return ( - - ); - } - - if (lazy) { - return ( - - } - > - - - ); - } - - return ( - - ); - } +function AlbumCover(props) { + return ( + + ); } AlbumCover.propTypes = { - className: PropTypes.string, - style: PropTypes.object, - images: PropTypes.arrayOf(PropTypes.object).isRequired, - size: PropTypes.number.isRequired, - lazy: PropTypes.bool.isRequired, - overflow: PropTypes.bool.isRequired + size: PropTypes.number.isRequired }; AlbumCover.defaultProps = { - size: 250, - lazy: true, - overflow: false + size: 250 }; export default AlbumCover; diff --git a/frontend/src/Album/Details/AlbumDetails.js b/frontend/src/Album/Details/AlbumDetails.js index 36fd795f7..f2288b00f 100644 --- a/frontend/src/Album/Details/AlbumDetails.js +++ b/frontend/src/Album/Details/AlbumDetails.js @@ -301,7 +301,7 @@ class AlbumDetails extends Component { diff --git a/frontend/src/Artist/ArtistImage.js b/frontend/src/Artist/ArtistImage.js index 576a354a1..6ae479a18 100644 --- a/frontend/src/Artist/ArtistImage.js +++ b/frontend/src/Artist/ArtistImage.js @@ -10,6 +10,7 @@ function getUrl(image, coverType, size) { if (image) { // Remove protocol let url = image.url.replace(/^https?:/, ''); + url = url.replace(`${coverType}.jpg`, `${coverType}-${size}.jpg`); return url; @@ -24,7 +25,7 @@ class ArtistImage extends Component { constructor(props, context) { super(props, context); - const pixelRatio = Math.floor(window.devicePixelRatio); + const pixelRatio = Math.ceil(window.devicePixelRatio); const { images, diff --git a/frontend/src/Artist/Details/ArtistDetails.js b/frontend/src/Artist/Details/ArtistDetails.js index 74dc1d4c6..85831a955 100644 --- a/frontend/src/Artist/Details/ArtistDetails.js +++ b/frontend/src/Artist/Details/ArtistDetails.js @@ -353,7 +353,7 @@ class ArtistDetails extends Component { diff --git a/src/Lidarr.Api.V1/Albums/AlbumModule.cs b/src/Lidarr.Api.V1/Albums/AlbumModule.cs index 25acab4c4..e09eb8204 100644 --- a/src/Lidarr.Api.V1/Albums/AlbumModule.cs +++ b/src/Lidarr.Api.V1/Albums/AlbumModule.cs @@ -12,6 +12,7 @@ using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Download; using NzbDrone.Core.Music.Events; using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.MediaCover; namespace Lidarr.Api.V1.Albums { @@ -23,13 +24,14 @@ namespace Lidarr.Api.V1.Albums { protected readonly IReleaseService _releaseService; - + public AlbumModule(IAlbumService albumService, IReleaseService releaseService, IArtistStatisticsService artistStatisticsService, + IMapCoversToLocal coverMapper, IUpgradableSpecification upgradableSpecification, IBroadcastSignalRMessage signalRBroadcaster) - : base(albumService, artistStatisticsService, upgradableSpecification, signalRBroadcaster) + : base(albumService, artistStatisticsService, coverMapper, upgradableSpecification, signalRBroadcaster) { _releaseService = releaseService; GetResourceAll = GetAlbums; diff --git a/src/Lidarr.Api.V1/Albums/AlbumModuleWithSignalR.cs b/src/Lidarr.Api.V1/Albums/AlbumModuleWithSignalR.cs index 3e44d469a..4c7b4f873 100644 --- a/src/Lidarr.Api.V1/Albums/AlbumModuleWithSignalR.cs +++ b/src/Lidarr.Api.V1/Albums/AlbumModuleWithSignalR.cs @@ -7,6 +7,7 @@ using NzbDrone.Core.Music; using NzbDrone.Core.ArtistStats; using NzbDrone.SignalR; using Lidarr.Http; +using NzbDrone.Core.MediaCover; namespace Lidarr.Api.V1.Albums { @@ -15,15 +16,18 @@ namespace Lidarr.Api.V1.Albums protected readonly IAlbumService _albumService; protected readonly IArtistStatisticsService _artistStatisticsService; protected readonly IUpgradableSpecification _qualityUpgradableSpecification; + protected readonly IMapCoversToLocal _coverMapper; protected AlbumModuleWithSignalR(IAlbumService albumService, IArtistStatisticsService artistStatisticsService, + IMapCoversToLocal coverMapper, IUpgradableSpecification qualityUpgradableSpecification, IBroadcastSignalRMessage signalRBroadcaster) : base(signalRBroadcaster) { _albumService = albumService; _artistStatisticsService = artistStatisticsService; + _coverMapper = coverMapper; _qualityUpgradableSpecification = qualityUpgradableSpecification; GetResourceById = GetAlbum; @@ -62,6 +66,7 @@ namespace Lidarr.Api.V1.Albums } FetchAndLinkAlbumStatistics(resource); + MapCoversToLocal(resource); return resource; } @@ -86,6 +91,7 @@ namespace Lidarr.Api.V1.Albums var artistStats = _artistStatisticsService.ArtistStatistics(); LinkArtistStatistics(result, artistStats); + MapCoversToLocal(result.ToArray()); return result; } @@ -114,5 +120,13 @@ namespace Lidarr.Api.V1.Albums } } + + private void MapCoversToLocal(params AlbumResource[] albums) + { + foreach (var albumResource in albums) + { + _coverMapper.ConvertToLocalUrls(albumResource.Id, MediaCoverEntity.Album, albumResource.Images); + } + } } } diff --git a/src/Lidarr.Api.V1/Artist/ArtistModule.cs b/src/Lidarr.Api.V1/Artist/ArtistModule.cs index f33c4a817..552ce404b 100644 --- a/src/Lidarr.Api.V1/Artist/ArtistModule.cs +++ b/src/Lidarr.Api.V1/Artist/ArtistModule.cs @@ -181,7 +181,7 @@ namespace Lidarr.Api.V1.Artist { foreach (var artistResource in artists) { - _coverMapper.ConvertToLocalUrls(artistResource.Id, artistResource.Images); + _coverMapper.ConvertToLocalUrls(artistResource.Id, MediaCoverEntity.Artist, artistResource.Images); } } diff --git a/src/Lidarr.Api.V1/MediaCovers/MediaCoverModule.cs b/src/Lidarr.Api.V1/MediaCovers/MediaCoverModule.cs index 8b5f880cf..70d0cee8c 100644 --- a/src/Lidarr.Api.V1/MediaCovers/MediaCoverModule.cs +++ b/src/Lidarr.Api.V1/MediaCovers/MediaCoverModule.cs @@ -12,7 +12,8 @@ namespace Lidarr.Api.V1.MediaCovers { private static readonly Regex RegexResizedImage = new Regex(@"-\d+\.jpg$", RegexOptions.Compiled | RegexOptions.IgnoreCase); - private const string MEDIA_COVER_ROUTE = @"/(?\d+)/(?(.+)\.(jpg|png|gif))"; + private const string MEDIA_COVER_ARTIST_ROUTE = @"/Artist/(?\d+)/(?(.+)\.(jpg|png|gif))"; + private const string MEDIA_COVER_ALBUM_ROUTE = @"/Album/(?\d+)/(?(.+)\.(jpg|png|gif))"; private readonly IAppFolderInfo _appFolderInfo; private readonly IDiskProvider _diskProvider; @@ -22,10 +23,11 @@ namespace Lidarr.Api.V1.MediaCovers _appFolderInfo = appFolderInfo; _diskProvider = diskProvider; - Get[MEDIA_COVER_ROUTE] = options => GetMediaCover(options.artistId, options.filename); + Get[MEDIA_COVER_ARTIST_ROUTE] = options => GetArtistMediaCover(options.artistId, options.filename); + Get[MEDIA_COVER_ALBUM_ROUTE] = options => GetAlbumMediaCover(options.artistId, options.filename); } - private Response GetMediaCover(int artistId, string filename) + private Response GetArtistMediaCover(int artistId, string filename) { var filePath = Path.Combine(_appFolderInfo.GetAppDataPath(), "MediaCover", artistId.ToString(), filename); @@ -43,5 +45,24 @@ namespace Lidarr.Api.V1.MediaCovers return new StreamResponse(() => File.OpenRead(filePath), MimeTypes.GetMimeType(filePath)); } + + private Response GetAlbumMediaCover(int albumId, string filename) + { + var filePath = Path.Combine(_appFolderInfo.GetAppDataPath(), "MediaCover", "Albums", albumId.ToString(), filename); + + if (!_diskProvider.FileExists(filePath) || _diskProvider.GetFileSize(filePath) == 0) + { + // Return the full sized image if someone requests a non-existing resized one. + // TODO: This code can be removed later once everyone had the update for a while. + var basefilePath = RegexResizedImage.Replace(filePath, ".jpg"); + if (basefilePath == filePath || !_diskProvider.FileExists(basefilePath)) + { + return new NotFoundResponse(); + } + filePath = basefilePath; + } + + return new StreamResponse(() => File.OpenRead(filePath), MimeTypes.GetMimeType(filePath)); + } } } diff --git a/src/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs b/src/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs index 4877cb48b..52e3e093f 100644 --- a/src/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs @@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.MediaCoverTests Mocker.GetMock().Setup(c => c.FileExists(It.IsAny())) .Returns(true); - Subject.ConvertToLocalUrls(12, covers); + Subject.ConvertToLocalUrls(12, MediaCoverEntity.Artist, covers); covers.Single().Url.Should().Be("/MediaCover/12/banner.jpg?lastWrite=1234"); @@ -75,10 +75,10 @@ namespace NzbDrone.Core.Test.MediaCoverTests Mocker.GetMock().Setup(c => c.FileExists(It.IsAny())) .Returns(true); - Subject.ConvertToLocalUrls(12, covers, 6); + Subject.ConvertToLocalUrls(6, MediaCoverEntity.Album, covers); - covers.Single().Url.Should().Be("/MediaCover/12/6/disc.jpg?lastWrite=1234"); + covers.Single().Url.Should().Be("/MediaCover/Albums/6/disc.jpg?lastWrite=1234"); } [Test] @@ -90,7 +90,7 @@ namespace NzbDrone.Core.Test.MediaCoverTests }; - Subject.ConvertToLocalUrls(12, covers); + Subject.ConvertToLocalUrls(12, MediaCoverEntity.Artist, covers); covers.Single().Url.Should().Be("/MediaCover/12/banner.jpg"); @@ -103,6 +103,10 @@ namespace NzbDrone.Core.Test.MediaCoverTests .Setup(v => v.AlreadyExists(It.IsAny(), It.IsAny())) .Returns(false); + Mocker.GetMock() + .Setup(v => v.GetAlbumsByArtist(It.IsAny())) + .Returns(new List { _album }); + Mocker.GetMock() .Setup(v => v.FileExists(It.IsAny())) .Returns(true); @@ -110,7 +114,7 @@ namespace NzbDrone.Core.Test.MediaCoverTests Subject.HandleAsync(new ArtistUpdatedEvent(_artist)); Mocker.GetMock() - .Verify(v => v.Resize(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + .Verify(v => v.Resize(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(3)); } [Test] @@ -120,6 +124,10 @@ namespace NzbDrone.Core.Test.MediaCoverTests .Setup(v => v.AlreadyExists(It.IsAny(), It.IsAny())) .Returns(true); + Mocker.GetMock() + .Setup(v => v.GetAlbumsByArtist(It.IsAny())) + .Returns(new List { _album }); + Mocker.GetMock() .Setup(v => v.FileExists(It.IsAny())) .Returns(false); @@ -127,7 +135,7 @@ namespace NzbDrone.Core.Test.MediaCoverTests Subject.HandleAsync(new ArtistUpdatedEvent(_artist)); Mocker.GetMock() - .Verify(v => v.Resize(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + .Verify(v => v.Resize(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(3)); } [Test] @@ -141,6 +149,10 @@ namespace NzbDrone.Core.Test.MediaCoverTests .Setup(v => v.FileExists(It.IsAny())) .Returns(true); + Mocker.GetMock() + .Setup(v => v.GetAlbumsByArtist(It.IsAny())) + .Returns(new List { _album }); + Mocker.GetMock() .Setup(v => v.GetFileSize(It.IsAny())) .Returns(1000); @@ -162,6 +174,10 @@ namespace NzbDrone.Core.Test.MediaCoverTests .Setup(v => v.FileExists(It.IsAny())) .Returns(true); + Mocker.GetMock() + .Setup(v => v.GetAlbumsByArtist(It.IsAny())) + .Returns(new List { _album }); + Mocker.GetMock() .Setup(v => v.GetFileSize(It.IsAny())) .Returns(0); @@ -169,7 +185,7 @@ namespace NzbDrone.Core.Test.MediaCoverTests Subject.HandleAsync(new ArtistUpdatedEvent(_artist)); Mocker.GetMock() - .Verify(v => v.Resize(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + .Verify(v => v.Resize(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(3)); } [Test] @@ -183,6 +199,10 @@ namespace NzbDrone.Core.Test.MediaCoverTests .Setup(v => v.FileExists(It.IsAny())) .Returns(false); + Mocker.GetMock() + .Setup(v => v.GetAlbumsByArtist(It.IsAny())) + .Returns(new List { _album }); + Mocker.GetMock() .Setup(v => v.Resize(It.IsAny(), It.IsAny(), It.IsAny())) .Throws(); @@ -190,7 +210,7 @@ namespace NzbDrone.Core.Test.MediaCoverTests Subject.HandleAsync(new ArtistUpdatedEvent(_artist)); Mocker.GetMock() - .Verify(v => v.Resize(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + .Verify(v => v.Resize(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(3)); } } } diff --git a/src/NzbDrone.Core/Extras/Metadata/Consumers/Roksbox/RoksboxMetadata.cs b/src/NzbDrone.Core/Extras/Metadata/Consumers/Roksbox/RoksboxMetadata.cs index 1cffae9d8..daf1368e0 100644 --- a/src/NzbDrone.Core/Extras/Metadata/Consumers/Roksbox/RoksboxMetadata.cs +++ b/src/NzbDrone.Core/Extras/Metadata/Consumers/Roksbox/RoksboxMetadata.cs @@ -165,7 +165,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox return new List(); ; } - var source = _mediaCoverService.GetCoverPath(artist.Id, image.CoverType); + var source = _mediaCoverService.GetCoverPath(artist.Id, MediaCoverEntity.Artist, image.CoverType); var destination = Path.GetFileName(artist.Path) + Path.GetExtension(source); return new List{ new ImageFileResult(destination, source) }; diff --git a/src/NzbDrone.Core/Extras/Metadata/Consumers/Xbmc/XbmcMetadata.cs b/src/NzbDrone.Core/Extras/Metadata/Consumers/Xbmc/XbmcMetadata.cs index ed91bc47c..c849b867e 100644 --- a/src/NzbDrone.Core/Extras/Metadata/Consumers/Xbmc/XbmcMetadata.cs +++ b/src/NzbDrone.Core/Extras/Metadata/Consumers/Xbmc/XbmcMetadata.cs @@ -205,7 +205,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc { foreach (var image in artist.Metadata.Value.Images) { - var source = _mediaCoverService.GetCoverPath(artist.Id, image.CoverType); + var source = _mediaCoverService.GetCoverPath(artist.Id, MediaCoverEntity.Artist, image.CoverType); var destination = image.CoverType.ToString().ToLowerInvariant() + Path.GetExtension(image.Url); if (image.CoverType == MediaCoverTypes.Poster) { diff --git a/src/NzbDrone.Core/MediaCover/MediaCover.cs b/src/NzbDrone.Core/MediaCover/MediaCover.cs index ed832e003..3829cb450 100644 --- a/src/NzbDrone.Core/MediaCover/MediaCover.cs +++ b/src/NzbDrone.Core/MediaCover/MediaCover.cs @@ -16,6 +16,12 @@ namespace NzbDrone.Core.MediaCover Logo = 8 } + public enum MediaCoverEntity + { + Artist = 0, + Album = 1 + } + public class MediaCover : IEmbeddedDocument { public MediaCoverTypes CoverType { get; set; } diff --git a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs index e64505893..09aa0a8d2 100644 --- a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs +++ b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net; using NLog; using NzbDrone.Common.Disk; @@ -16,8 +17,8 @@ namespace NzbDrone.Core.MediaCover { public interface IMapCoversToLocal { - void ConvertToLocalUrls(int artistId, IEnumerable covers, int? albumId = null); - string GetCoverPath(int artistId, MediaCoverTypes mediaCoverTypes, int? height = null, int? albumId = null); + void ConvertToLocalUrls(int entityId, MediaCoverEntity coverEntity, IEnumerable covers); + string GetCoverPath(int entityId, MediaCoverEntity coverEntity, MediaCoverTypes mediaCoverTypes, int? height = null); } public class MediaCoverService : @@ -58,31 +59,33 @@ namespace NzbDrone.Core.MediaCover _coverRootFolder = appFolderInfo.GetMediaCoverPath(); } - public string GetCoverPath(int artistId, MediaCoverTypes coverTypes, int? height = null, int? albumId = null) + public string GetCoverPath(int entityId, MediaCoverEntity coverEntity, MediaCoverTypes coverTypes, int? height = null) { var heightSuffix = height.HasValue ? "-" + height.ToString() : ""; - if (albumId.HasValue) + if (coverEntity == MediaCoverEntity.Album) { - return Path.Combine(GetAlbumCoverPath(artistId, albumId.Value), coverTypes.ToString().ToLower() + heightSuffix + ".jpg"); + return Path.Combine(GetAlbumCoverPath(entityId), coverTypes.ToString().ToLower() + heightSuffix + ".jpg"); + } + else + { + return Path.Combine(GetArtistCoverPath(entityId), coverTypes.ToString().ToLower() + heightSuffix + ".jpg"); } - - return Path.Combine(GetArtistCoverPath(artistId), coverTypes.ToString().ToLower() + heightSuffix + ".jpg"); } - public void ConvertToLocalUrls(int artistId, IEnumerable covers, int? albumId = null) + public void ConvertToLocalUrls(int entityId, MediaCoverEntity coverEntity, IEnumerable covers) { foreach (var mediaCover in covers) { - var filePath = GetCoverPath(artistId, mediaCover.CoverType, null, albumId); + var filePath = GetCoverPath(entityId, coverEntity, mediaCover.CoverType, null); - if (albumId.HasValue) + if (coverEntity == MediaCoverEntity.Album) { - mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/" + artistId + "/" + albumId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg"; + mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/Albums/" + entityId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg"; } else { - mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/" + artistId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg"; + mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/" + entityId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg"; } if (_diskProvider.FileExists(filePath)) @@ -98,16 +101,16 @@ namespace NzbDrone.Core.MediaCover return Path.Combine(_coverRootFolder, artistId.ToString()); } - private string GetAlbumCoverPath(int artistId, int albumId) + private string GetAlbumCoverPath(int albumId) { - return Path.Combine(_coverRootFolder, artistId.ToString(), albumId.ToString()); + return Path.Combine(_coverRootFolder, "Albums", albumId.ToString()); } - private void EnsureCovers(Artist artist) + private void EnsureArtistCovers(Artist artist) { foreach (var cover in artist.Metadata.Value.Images) { - var fileName = GetCoverPath(artist.Id, cover.CoverType); + var fileName = GetCoverPath(artist.Id, MediaCoverEntity.Artist, cover.CoverType); var alreadyExists = false; try @@ -134,37 +137,37 @@ namespace NzbDrone.Core.MediaCover } } - //TODO Decide if we want to cache album art local - //private void EnsureAlbumCovers(Album album) - //{ - // foreach (var cover in album.Images) - // { - // var fileName = GetCoverPath(album.ArtistId, cover.CoverType, null, album.Id); - // var alreadyExists = false; - // try - // { - // alreadyExists = _coverExistsSpecification.AlreadyExists(cover.Url, fileName); - // if (!alreadyExists) - // { - // DownloadAlbumCover(album, cover); - // } - // } - // catch (WebException e) - // { - // _logger.Warn("Couldn't download media cover for {0}. {1}", album, e.Message); - // } - // catch (Exception e) - // { - // _logger.Error(e, "Couldn't download media cover for {0}", album); - // } - - // EnsureResizedCovers(album.Artist, cover, !alreadyExists, album); - // } - //} + private void EnsureAlbumCovers(Album album) + { + foreach (var cover in album.Images.Where(e => e.CoverType == MediaCoverTypes.Cover)) + { + var fileName = GetCoverPath(album.Id, MediaCoverEntity.Album, cover.CoverType, null); + var alreadyExists = false; + try + { + var lastModifiedServer = GetCoverModifiedDate(cover.Url); + alreadyExists = _coverExistsSpecification.AlreadyExists(lastModifiedServer, fileName); + if (!alreadyExists) + { + DownloadAlbumCover(album, cover, lastModifiedServer); + } + } + catch (WebException e) + { + _logger.Warn("Couldn't download media cover for {0}. {1}", album, e.Message); + } + catch (Exception e) + { + _logger.Error(e, "Couldn't download media cover for {0}", album); + } + + EnsureResizedAlbumCovers(album, cover, !alreadyExists); + } + } private void DownloadCover(Artist artist, MediaCover cover, DateTime lastModified) { - var fileName = GetCoverPath(artist.Id, cover.CoverType); + var fileName = GetCoverPath(artist.Id, MediaCoverEntity.Artist, cover.CoverType); _logger.Info("Downloading {0} for {1} {2}", cover.CoverType, artist, cover.Url); _httpClient.DownloadFile(cover.Url, fileName); @@ -179,124 +182,132 @@ namespace NzbDrone.Core.MediaCover } } - //private void DownloadAlbumCover(Album album, MediaCover cover) - //{ - // var fileName = GetCoverPath(album.ArtistId, cover.CoverType, null, album.Id); + private void DownloadAlbumCover(Album album, MediaCover cover, DateTime lastModified) + { + var fileName = GetCoverPath(album.Id, MediaCoverEntity.Album, cover.CoverType, null); - // _logger.Info("Downloading {0} for {1} {2}", cover.CoverType, album, cover.Url); - // _httpClient.DownloadFile(cover.Url, fileName); - //} + _logger.Info("Downloading {0} for {1} {2}", cover.CoverType, album, cover.Url); + _httpClient.DownloadFile(cover.Url, fileName); + + try + { + _diskProvider.FileSetLastWriteTime(fileName, lastModified); + } + catch (Exception ex) + { + _logger.Debug(ex, "Unable to set modified date for {0} image for album {1}", cover.CoverType, album); + } + } private void EnsureResizedCovers(Artist artist, MediaCover cover, bool forceResize, Album album = null) { - int[] heights; + int[] heights = GetDefaultHeights(cover.CoverType); - switch (cover.CoverType) + foreach (var height in heights) { - default: - return; - - case MediaCoverTypes.Poster: - case MediaCoverTypes.Cover: - case MediaCoverTypes.Disc: - case MediaCoverTypes.Logo: - case MediaCoverTypes.Headshot: - heights = new[] { 500, 250 }; - break; - - case MediaCoverTypes.Banner: - heights = new[] { 70, 35 }; - break; + var mainFileName = GetCoverPath(artist.Id, MediaCoverEntity.Artist, cover.CoverType); + var resizeFileName = GetCoverPath(artist.Id, MediaCoverEntity.Artist, cover.CoverType, height); - case MediaCoverTypes.Fanart: - case MediaCoverTypes.Screenshot: - heights = new[] { 360, 180 }; - break; - } - - - if (album == null) - { - foreach (var height in heights) + if (forceResize || !_diskProvider.FileExists(resizeFileName) || _diskProvider.GetFileSize(resizeFileName) == 0) { - var mainFileName = GetCoverPath(artist.Id, cover.CoverType); - var resizeFileName = GetCoverPath(artist.Id, cover.CoverType, height); + _logger.Debug("Resizing {0}-{1} for {2}", cover.CoverType, height, artist); - if (forceResize || !_diskProvider.FileExists(resizeFileName) || _diskProvider.GetFileSize(resizeFileName) == 0) + try + { + _resizer.Resize(mainFileName, resizeFileName, height); + } + catch { - _logger.Debug("Resizing {0}-{1} for {2}", cover.CoverType, height, artist); - - try - { - _resizer.Resize(mainFileName, resizeFileName, height); - } - catch - { - _logger.Debug("Couldn't resize media cover {0}-{1} for {2}, using full size image instead.", cover.CoverType, height, artist); - } + _logger.Debug("Couldn't resize media cover {0}-{1} for artist {2}, using full size image instead.", cover.CoverType, height, artist); } } } - else + } + + private void EnsureResizedAlbumCovers(Album album, MediaCover cover, bool forceResize) + { + int[] heights = GetDefaultHeights(cover.CoverType); + + foreach (var height in heights) { - foreach (var height in heights) - { - var mainFileName = GetCoverPath(album.ArtistId, cover.CoverType, null, album.Id); - var resizeFileName = GetCoverPath(album.ArtistId, cover.CoverType, height, album.Id); + var mainFileName = GetCoverPath(album.Id, MediaCoverEntity.Album, cover.CoverType, null); + var resizeFileName = GetCoverPath(album.Id, MediaCoverEntity.Album, cover.CoverType, height); - if (forceResize || !_diskProvider.FileExists(resizeFileName) || _diskProvider.GetFileSize(resizeFileName) == 0) + if (forceResize || !_diskProvider.FileExists(resizeFileName) || _diskProvider.GetFileSize(resizeFileName) == 0) + { + _logger.Debug("Resizing {0}-{1} for {2}", cover.CoverType, height, album); + + try + { + _resizer.Resize(mainFileName, resizeFileName, height); + } + catch { - _logger.Debug("Resizing {0}-{1} for {2}", cover.CoverType, height, artist); - - try - { - _resizer.Resize(mainFileName, resizeFileName, height); - } - catch - { - _logger.Debug("Couldn't resize media cover {0}-{1} for {2}, using full size image instead.", cover.CoverType, height, album); - } + _logger.Debug("Couldn't resize media cover {0}-{1} for album {2}, using full size image instead.", cover.CoverType, height, album); } } } } - public void HandleAsync(ArtistUpdatedEvent message) + private DateTime GetCoverModifiedDate(string url) { - EnsureCovers(message.Artist); + var lastModifiedServer = DateTime.Now; - //Turn off for now, not using album images + var headers = _httpClient.Head(new HttpRequest(url)).Headers; - //var albums = _albumService.GetAlbumsByArtist(message.Artist.Id); - //foreach (Album album in albums) - //{ - // EnsureAlbumCovers(album); - //} - - _eventAggregator.PublishEvent(new MediaCoversUpdatedEvent(message.Artist)); + if (headers.LastModified.HasValue) + { + lastModifiedServer = headers.LastModified.Value; + } + + return lastModifiedServer; } - public void HandleAsync(ArtistDeletedEvent message) + private int[] GetDefaultHeights(MediaCoverTypes coverType) { - var path = GetArtistCoverPath(message.Artist.Id); - if (_diskProvider.FolderExists(path)) + switch (coverType) { - _diskProvider.DeleteFolder(path, true); + default: + return new int[] { }; + + case MediaCoverTypes.Poster: + case MediaCoverTypes.Disc: + case MediaCoverTypes.Logo: + case MediaCoverTypes.Headshot: + return new[] { 500, 250 }; + + case MediaCoverTypes.Banner: + return new[] { 70, 35 }; + + case MediaCoverTypes.Fanart: + case MediaCoverTypes.Screenshot: + return new[] { 360, 180 }; + case MediaCoverTypes.Cover: + return new[] { 250 }; } } - private DateTime GetCoverModifiedDate(string url) + public void HandleAsync(ArtistUpdatedEvent message) { - var lastModifiedServer = DateTime.Now; + EnsureArtistCovers(message.Artist); - var headers = _httpClient.Head(new HttpRequest(url)).Headers; - - if (headers.LastModified.HasValue) + var albums = _albumService.GetAlbumsByArtist(message.Artist.Id); + foreach (Album album in albums) { - lastModifiedServer = headers.LastModified.Value; + EnsureAlbumCovers(album); } - return lastModifiedServer; + _eventAggregator.PublishEvent(new MediaCoversUpdatedEvent(message.Artist)); } + + public void HandleAsync(ArtistDeletedEvent message) + { + var path = GetArtistCoverPath(message.Artist.Id); + if (_diskProvider.FolderExists(path)) + { + _diskProvider.DeleteFolder(path, true); + } + } + } }