using Sony.Filtr.Contracts.Abstractions; using Sony.Filtr.SpotifyWebAPI.Model; using System; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; using NLog; using Image = System.Drawing.Image; namespace Sony.Filtr.SpotifyImages { public class SpotifyImageDownloader { private readonly HttpClient _httpClient; private readonly IImageFactory _imageFactory; private readonly SpotifyImageManager _spotifyImageManager; private readonly Logger _logger; public SpotifyImageDownloader(HttpClient httpClient, IImageFactory imageFactory, SpotifyImageManager spotifyImageManager) { _httpClient = httpClient; _imageFactory = imageFactory; _spotifyImageManager = spotifyImageManager; _logger = LogManager.GetLogger("SpotifyImageDownloader"); } public async Task StoreLargestPlaylistImageAsync(string playlistId, string url) { if (string.IsNullOrWhiteSpace(playlistId) || string.IsNullOrWhiteSpace(url)) { //We just bail, might want to throw exception when all callers handle it. return null; } var playlistData = await _spotifyImageManager.GetCachedImageDataAsync(playlistId); if (playlistData != null) { //_logger.Debug($"Found playlist cache data for {playlistId}: {playlistData.LastModified}"); } string filename = "playlist-" + playlistId + ".jpeg"; var imageResponse = await DownloadImageAsync(url, playlistData?.LastModified, playlistData?.ETag); if (imageResponse.StatusCode == HttpStatusCode.OK) { using(var imageResponseStream = await imageResponse.Content.ReadAsStreamAsync()) { using(var image = Image.FromStream(imageResponseStream)) { using(var output = new MemoryStream()) { image.Save(output, ImageFormat.Jpeg); var hash = CalculateMd5Hash(output); if (hash != playlistData?.ImageDataHash) { await _imageFactory.SaveImageAsync(filename, output); var localImageData = new PlaylistImageData() { PlaylistId = playlistId, ETag = imageResponse.Headers.ETag?.Tag, LastModified = imageResponse.Content.Headers.LastModified?.DateTime, ImageDataHash = hash, SpotifyImageUrl = url, }; //_logger.Debug($"Saved image for {playlistId}"); if (playlistData != null) { _spotifyImageManager.UpdateLocalImageData(localImageData); } else { _spotifyImageManager.AddLocalImageData(localImageData); } //_logger.Debug($"Done with {playlistId}, filename is {filename}"); } else { //_logger.Debug($"Nice! we didn't need to upload the image for {playlistId} because the data has not changed"); } return filename; } } } } else if (imageResponse.StatusCode == HttpStatusCode.NotModified) { //_logger.Debug($"Swell, we didn't need to download the image for {playlistId} because it has not changed"); return filename; } return null; } public async Task SaveUserImageAsync(UserResponse user) { var userImageUrl = user.images?.FirstOrDefault(i => !string.IsNullOrWhiteSpace(i.url)); if (userImageUrl == null) return null; var filename = $"user-{user.id}.jpeg"; return await SaveImageAsync(userImageUrl, filename); } public async Task SaveLargestAlbumImageAsync(FullAlbumItem album) { var albumImage = album.images?.OrderByDescending(i => i.width).FirstOrDefault(); if (albumImage == null) return null; var filename = "album-" + album.id + "-large.jpeg"; return await SaveImageAsync(albumImage, filename); } public async Task SaveSmallestAlbumImageAsync(FullAlbumItem album) { var albumImage = album.images?.OrderBy(i => i.width).FirstOrDefault(); if (albumImage == null) return null; var filename = "album-" + album.id + "-small.jpeg"; return await SaveImageAsync(albumImage, filename); } private async Task SaveImageAsync(SpotifyWebAPI.Model.Image image, string filename) { var imageUrl = image.url; try { var response = await _httpClient.GetAsync(imageUrl); var responseStream = await response.Content.ReadAsStreamAsync(); using(var imageData = Image.FromStream(responseStream)) { using(var output = new MemoryStream()) { imageData.Save(output, ImageFormat.Jpeg); //_logger.Info("Saving image to Amazon for album {0}", album.id); await _imageFactory.SaveImageAsync(filename, output); } } return filename; } catch(Exception ex) { //_logger.Error("Could not get image for {0}. Exception: {1}", album.name, ex.ToString()); } return filename; } private async Task DownloadImageAsync(string imageUrl, DateTimeOffset? lastModified, string etag) { //_logger.Debug($"Requesting {imageUrl}"); var requestMessage = new HttpRequestMessage(HttpMethod.Get, imageUrl); if (lastModified.HasValue) requestMessage.Headers.IfModifiedSince = lastModified.Value; if (!string.IsNullOrWhiteSpace(etag)) requestMessage.Headers.IfNoneMatch.Add(new EntityTagHeaderValue(etag)); return await _httpClient.SendAsync(requestMessage); } private string CalculateMd5Hash(MemoryStream output) { // Use input string to calculate MD5 hash using (var md5 = System.Security.Cryptography.MD5.Create()) { var hashBytes = md5.ComputeHash(output.ToArray()); // Convert the byte array to hexadecimal string var sb = new StringBuilder(); foreach(var t in hashBytes) { sb.Append(t.ToString("X2")); } return sb.ToString(); } } } }