From f97ed62fae66b822c27a54b5406e943e3aa6edfd Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Fri, 5 May 2017 12:57:58 -0500 Subject: [PATCH 1/3] Partially implemented artist overview. Needs review --- src/NzbDrone.Api/Music/ArtistModule.cs | 15 +---- src/NzbDrone.Api/Music/ArtistResource.cs | 5 +- .../Cloud/SonarrCloudRequestBuilder.cs | 6 ++ .../Datastore/Migration/111_setup_music.cs | 1 + .../SkyHook/Resource/ArtistResource.cs | 37 ++++++++++ .../MetadataSource/SkyHook/SkyHookProxy.cs | 67 ++++++++++++++++++- src/NzbDrone.Core/Music/Artist.cs | 1 + 7 files changed, 114 insertions(+), 18 deletions(-) diff --git a/src/NzbDrone.Api/Music/ArtistModule.cs b/src/NzbDrone.Api/Music/ArtistModule.cs index 1f7c433cf..c598713d6 100644 --- a/src/NzbDrone.Api/Music/ArtistModule.cs +++ b/src/NzbDrone.Api/Music/ArtistModule.cs @@ -98,7 +98,6 @@ namespace NzbDrone.Api.Music { //var seriesStats = _seriesStatisticsService.SeriesStatistics(); var artistResources = _artistService.GetAllArtists().ToResource(); - Console.WriteLine("[DEBUG] Returning {0} Artists", artistResources.Count); MapCoversToLocal(artistResources.ToArray()); //LinkSeriesStatistics(seriesResources, seriesStats); //PopulateAlternateTitles(seriesResources); @@ -106,9 +105,9 @@ namespace NzbDrone.Api.Music return artistResources; } - private int AddArtist(ArtistResource seriesResource) + private int AddArtist(ArtistResource artistResource) { - var model = seriesResource.ToModel(); + var model = artistResource.ToModel(); return _addSeriesService.AddArtist(model).Id; } @@ -175,16 +174,6 @@ namespace NzbDrone.Api.Music BroadcastResourceChange(ModelAction.Updated, message.Artist.Id); } - //public void Handle(ArtistDeletedEvent message) - //{ - // BroadcastResourceChange(ModelAction.Deleted, message.Artist.ToResource()); - //} - - //public void Handle(ArtistRenamedEvent message) - //{ - // BroadcastResourceChange(ModelAction.Updated, message.Artist.Id); - //} - //public void Handle(MediaCoversUpdatedEvent message) //{ // BroadcastResourceChange(ModelAction.Updated, message.Artist.Id); diff --git a/src/NzbDrone.Api/Music/ArtistResource.cs b/src/NzbDrone.Api/Music/ArtistResource.cs index c42c8f4d7..89bc764a3 100644 --- a/src/NzbDrone.Api/Music/ArtistResource.cs +++ b/src/NzbDrone.Api/Music/ArtistResource.cs @@ -22,6 +22,7 @@ namespace NzbDrone.Api.Music public int ItunesId { get; set; } //public List AlternateTitles { get; set; } //public string SortTitle { get; set; } + public string Overview { get; set; } public int AlbumCount { @@ -82,7 +83,7 @@ namespace NzbDrone.Api.Music //EpisodeFileCount //SizeOnDisk //Status = resource.Status, - //Overview = resource.Overview, + Overview = model.Overview, //NextAiring //PreviousAiring //Network = resource.Network, @@ -135,7 +136,7 @@ namespace NzbDrone.Api.Music //EpisodeFileCount //SizeOnDisk //Status = resource.Status, - //Overview = resource.Overview, + Overview = resource.Overview, //NextAiring //PreviousAiring //Network = resource.Network, diff --git a/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs b/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs index 29f0e9d78..fa734300a 100644 --- a/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs +++ b/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs @@ -6,6 +6,7 @@ namespace NzbDrone.Common.Cloud { IHttpRequestBuilderFactory Services { get; } IHttpRequestBuilderFactory Search { get; } + IHttpRequestBuilderFactory InternalSearch { get; } IHttpRequestBuilderFactory SkyHookTvdb { get; } } @@ -19,6 +20,9 @@ namespace NzbDrone.Common.Cloud Search = new HttpRequestBuilder("https://itunes.apple.com/{route}/") .CreateFactory(); + InternalSearch = new HttpRequestBuilder("https://itunes.apple.com/WebObjects/MZStore.woa/wa/{route}") //viewArtist or search + .CreateFactory(); + SkyHookTvdb = new HttpRequestBuilder("http://skyhook.lidarr.tv/v1/tvdb/{route}/{language}/") .SetSegment("language", "en") .CreateFactory(); @@ -28,6 +32,8 @@ namespace NzbDrone.Common.Cloud public IHttpRequestBuilderFactory Search { get; } + public IHttpRequestBuilderFactory InternalSearch { get; } + public IHttpRequestBuilderFactory SkyHookTvdb { get; } } } diff --git a/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs b/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs index faa998a0a..8a342e20c 100644 --- a/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs +++ b/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs @@ -18,6 +18,7 @@ namespace NzbDrone.Core.Datastore.Migration .WithColumn("ArtistSlug").AsString().Nullable() //.Unique() .WithColumn("CleanTitle").AsString().Nullable() // Do we need this? .WithColumn("Monitored").AsBoolean() + .WithColumn("Overview").AsString().Nullable() .WithColumn("AlbumFolder").AsBoolean().Nullable() .WithColumn("ArtistFolder").AsBoolean().Nullable() .WithColumn("LastInfoSync").AsDateTime().Nullable() diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs index 88ad6eae6..5113d5394 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs @@ -5,6 +5,42 @@ using System.Text; namespace NzbDrone.Core.MetadataSource.SkyHook.Resource { + public class StorePlatformDataResource + { + public StorePlatformDataResource() { } + public ArtistInfoResource Artist { get; set; } + //public Lockup lockup { get; set; } + } + + public class ArtistInfoResource + { + public ArtistInfoResource() { } + public Dictionary Results { get; set; } + + public bool HasArtistBio { get; set; } + + public string url { get; set; } + public string shortUrl { get; set; } + + public List artistContemporaries { get; set; } + public List genreNames { get; set; } + public bool hasSocialPosts { get; set; } + public string artistBio { get; set; } + public bool isGroup { get; set; } + public string id { get; set; } + public string bornOrFormed { get; set; } + public string name { get; set; } + public string latestAlbumContentId { get; set; } + public string nameRaw { get; set; } + + //public string kind { get; set; } + //public List gallery { get; set; } + //public List genres { get; set; } + public List artistInfluencers { get; set; } + public List artistFollowers { get; set; } + //public string umcArtistImageUrl { get; set; } + } + public class AlbumResource { public AlbumResource() @@ -37,5 +73,6 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource public List Results { get; set; } //public string ArtistName { get; set; } //public List Albums { get; set; } + public StorePlatformDataResource StorePlatformData { get; set; } } } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index e7b97350b..16f3b3a50 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -22,11 +22,13 @@ namespace NzbDrone.Core.MetadataSource.SkyHook private readonly Logger _logger; private readonly IHttpRequestBuilderFactory _requestBuilder; + private readonly IHttpRequestBuilderFactory _internalRequestBuilder; public SkyHookProxy(IHttpClient httpClient, ILidarrCloudRequestBuilder requestBuilder, Logger logger) { _httpClient = httpClient; _requestBuilder = requestBuilder.Search; + _internalRequestBuilder = requestBuilder.InternalSearch; _logger = logger; } @@ -124,14 +126,59 @@ namespace NzbDrone.Core.MetadataSource.SkyHook } } + //public Artist GetArtistInfo(int itunesId) + //{ + // Console.WriteLine("[GetArtistInfo] id:" + itunesId); + // //https://itunes.apple.com/lookup?id=909253 + // //var httpRequest = _requestBuilder.Create() + // // .SetSegment("route", "lookup") + // // .AddQueryParam("id", itunesId.ToString()) + // // .Build(); + + // // TODO: Add special header, add Overview to Artist model + // var httpRequest = _requestBuilder.Create() + // .SetSegment("route", "viewArtist") + // .AddQueryParam("id", itunesId.ToString()) + // .Build(); + // httpRequest.Headers.Add("X-Apple-Store-Front", "143459-2,32 t:music3"); + + // httpRequest.AllowAutoRedirect = true; + // httpRequest.SuppressHttpError = true; + + // var httpResponse = _httpClient.Get(httpRequest); + + // if (httpResponse.HasHttpError) + // { + // if (httpResponse.StatusCode == HttpStatusCode.NotFound) + // { + // throw new ArtistNotFoundException(itunesId); + // } + // else + // { + // throw new HttpException(httpRequest, httpResponse); + // } + // } + + // Console.WriteLine("GetArtistInfo, GetArtistInfo"); + // return MapArtists(httpResponse.Resource)[0]; + //} + public Tuple> GetArtistInfo(int itunesId) { Console.WriteLine("[GetArtistInfo] id:" + itunesId); //https://itunes.apple.com/lookup?id=909253 - var httpRequest = _requestBuilder.Create() - .SetSegment("route", "lookup") + //var httpRequest = _requestBuilder.Create() + // .SetSegment("route", "lookup") + // .AddQueryParam("id", itunesId.ToString()) + // .Build(); + + // TODO: Add special header, add Overview to Artist model + var httpRequest = _internalRequestBuilder.Create() + .SetSegment("route", "viewArtist") .AddQueryParam("id", itunesId.ToString()) .Build(); + httpRequest.Headers.Add("X-Apple-Store-Front", "143459-2,32 t:music3"); + httpRequest.Headers.ContentType = "application/json"; httpRequest.AllowAutoRedirect = true; httpRequest.SuppressHttpError = true; @@ -154,7 +201,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook //var tracks = httpResponse.Resource.Episodes.Select(MapEpisode); //var artist = MapArtist(httpResponse.Resource); // I don't know how we are getting tracks from iTunes yet. - return new Tuple>(MapArtists(httpResponse.Resource)[0], new List()); + return new Tuple>(MapArtistInfo(httpResponse.Resource.StorePlatformData.Artist.Results[0]), new List()); //return new Tuple>(artist, tracks.ToList()); } public List SearchForNewArtist(string title) @@ -208,6 +255,20 @@ namespace NzbDrone.Core.MetadataSource.SkyHook } } + private Artist MapArtistInfo(ArtistInfoResource resource) + { + // This expects ArtistInfoResource, thus just need to populate one artist + Artist artist = new Artist(); + artist.Overview = resource.artistBio; + artist.ArtistName = resource.name; + foreach(var genre in resource.genreNames) + { + artist.Genres.Add(genre); + } + + return artist; + } + private List MapArtists(ArtistResource resource) { Album tempAlbum; diff --git a/src/NzbDrone.Core/Music/Artist.cs b/src/NzbDrone.Core/Music/Artist.cs index 2fde7c0ac..457176bab 100644 --- a/src/NzbDrone.Core/Music/Artist.cs +++ b/src/NzbDrone.Core/Music/Artist.cs @@ -26,6 +26,7 @@ namespace NzbDrone.Core.Music public string ArtistName { get; set; } public string ArtistSlug { get; set; } public string CleanTitle { get; set; } + public string Overview { get; set; } public bool Monitored { get; set; } public bool AlbumFolder { get; set; } public bool ArtistFolder { get; set; } From 3662bb933bf94926e199529363c2091103a26905 Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Fri, 5 May 2017 13:35:28 -0500 Subject: [PATCH 2/3] Fixed up API to instead call multiple APIs and mash results together --- .../MetadataSource/SkyHook/SkyHookProxy.cs | 45 ++++++++++--------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index 16f3b3a50..735ee3716 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -165,25 +165,24 @@ namespace NzbDrone.Core.MetadataSource.SkyHook public Tuple> GetArtistInfo(int itunesId) { - Console.WriteLine("[GetArtistInfo] id:" + itunesId); - //https://itunes.apple.com/lookup?id=909253 - //var httpRequest = _requestBuilder.Create() - // .SetSegment("route", "lookup") - // .AddQueryParam("id", itunesId.ToString()) - // .Build(); - - // TODO: Add special header, add Overview to Artist model - var httpRequest = _internalRequestBuilder.Create() + _logger.Debug("Getting Artist with iTunesID of {0}", itunesId); + var httpRequest1 = _requestBuilder.Create() + .SetSegment("route", "lookup") + .AddQueryParam("id", itunesId.ToString()) + .Build(); + + var httpRequest2 = _internalRequestBuilder.Create() .SetSegment("route", "viewArtist") .AddQueryParam("id", itunesId.ToString()) .Build(); - httpRequest.Headers.Add("X-Apple-Store-Front", "143459-2,32 t:music3"); - httpRequest.Headers.ContentType = "application/json"; + httpRequest2.Headers.Add("X-Apple-Store-Front", "143459-2,32 t:music3"); + httpRequest2.Headers.ContentType = "application/json"; - httpRequest.AllowAutoRedirect = true; - httpRequest.SuppressHttpError = true; + httpRequest1.AllowAutoRedirect = true; + httpRequest1.SuppressHttpError = true; - var httpResponse = _httpClient.Get(httpRequest); + var httpResponse = _httpClient.Get(httpRequest1); + var httpResponse2 = _httpClient.Get(httpRequest2); if (httpResponse.HasHttpError) { @@ -193,16 +192,22 @@ namespace NzbDrone.Core.MetadataSource.SkyHook } else { - throw new HttpException(httpRequest, httpResponse); + throw new HttpException(httpRequest1, httpResponse); + } + } + + List artists = MapArtists(httpResponse.Resource); + + if (httpResponse2.HasHttpError) + { + if (artists.Count == 1) + { + artists[0].Overview = httpResponse2.Resource.StorePlatformData.Artist.Results[itunesId].artistBio; } } - Console.WriteLine("GetArtistInfo, GetArtistInfo"); - //var tracks = httpResponse.Resource.Episodes.Select(MapEpisode); - //var artist = MapArtist(httpResponse.Resource); // I don't know how we are getting tracks from iTunes yet. - return new Tuple>(MapArtistInfo(httpResponse.Resource.StorePlatformData.Artist.Results[0]), new List()); - //return new Tuple>(artist, tracks.ToList()); + return new Tuple>(artists[0], new List()); } public List SearchForNewArtist(string title) { From 6aff6de378b1d221731236c3cb24343239055975 Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Fri, 5 May 2017 14:33:46 -0500 Subject: [PATCH 3/3] Added Artist Overview. --- .../MetadataSource/SkyHook/SkyHookProxy.cs | 47 +++++++++++++++---- src/NzbDrone.Core/Music/AddArtistService.cs | 7 ++- src/UI/Handlebars/Helpers/Series.js | 12 +++++ .../SeriesOverviewItemViewTemplate.hbs | 10 ++-- src/UI/Series/Index/SeriesIndexLayout.js | 1 + src/UI/Series/series.less | 6 +++ 6 files changed, 64 insertions(+), 19 deletions(-) diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index 735ee3716..84600ae18 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -182,7 +182,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook httpRequest1.SuppressHttpError = true; var httpResponse = _httpClient.Get(httpRequest1); - var httpResponse2 = _httpClient.Get(httpRequest2); + if (httpResponse.HasHttpError) { @@ -197,18 +197,18 @@ namespace NzbDrone.Core.MetadataSource.SkyHook } List artists = MapArtists(httpResponse.Resource); - - if (httpResponse2.HasHttpError) + List newArtists = new List(artists.Count); + int count = 0; + foreach (var artist in artists) { - if (artists.Count == 1) - { - artists[0].Overview = httpResponse2.Resource.StorePlatformData.Artist.Results[itunesId].artistBio; - } + newArtists.Add(AddOverview(artist)); + count++; } // I don't know how we are getting tracks from iTunes yet. - return new Tuple>(artists[0], new List()); + return new Tuple>(newArtists[0], new List()); } + public List SearchForNewArtist(string title) { try @@ -247,7 +247,18 @@ namespace NzbDrone.Core.MetadataSource.SkyHook var httpResponse = _httpClient.Get(httpRequest); - return MapArtists(httpResponse.Resource); + + List artists = MapArtists(httpResponse.Resource); + List newArtists = new List(artists.Count); + int count = 0; + foreach (var artist in artists) + { + newArtists.Add(AddOverview(artist)); + count++; + } + + + return newArtists; } catch (HttpException) { @@ -260,6 +271,24 @@ namespace NzbDrone.Core.MetadataSource.SkyHook } } + private Artist AddOverview(Artist artist) + { + var httpRequest = _internalRequestBuilder.Create() + .SetSegment("route", "viewArtist") + .AddQueryParam("id", artist.ItunesId.ToString()) + .Build(); + httpRequest.Headers.Add("X-Apple-Store-Front", "143459-2,32 t:music3"); + httpRequest.Headers.ContentType = "application/json"; + var httpResponse = _httpClient.Get(httpRequest); + + if (!httpResponse.HasHttpError) + { + artist.Overview = httpResponse.Resource.StorePlatformData.Artist.Results[artist.ItunesId].artistBio; + } + + return artist; + } + private Artist MapArtistInfo(ArtistInfoResource resource) { // This expects ArtistInfoResource, thus just need to populate one artist diff --git a/src/NzbDrone.Core/Music/AddArtistService.cs b/src/NzbDrone.Core/Music/AddArtistService.cs index 072a67754..3ca636d0b 100644 --- a/src/NzbDrone.Core/Music/AddArtistService.cs +++ b/src/NzbDrone.Core/Music/AddArtistService.cs @@ -79,18 +79,17 @@ namespace NzbDrone.Core.Music } catch (SeriesNotFoundException) { - _logger.Error("tvdbid {1} was not found, it may have been removed from TheTVDB.", newArtist.ItunesId); + _logger.Error("iTunesId {1} was not found, it may have been removed from iTunes.", newArtist.ItunesId); throw new ValidationException(new List { - new ValidationFailure("TvdbId", "A series with this ID was not found", newArtist.ItunesId) + new ValidationFailure("iTunesId", "An artist with this ID was not found", newArtist.ItunesId) }); } var artist = tuple.Item1; - // If seasons were passed in on the new series use them, otherwise use the seasons from Skyhook - // TODO: Refactor for albums + // If albums were passed in on the new artist use them, otherwise use the albums from Skyhook newArtist.Albums = newArtist.Albums != null && newArtist.Albums.Any() ? newArtist.Albums : artist.Albums; artist.ApplyChanges(newArtist); diff --git a/src/UI/Handlebars/Helpers/Series.js b/src/UI/Handlebars/Helpers/Series.js index ecade70eb..4003a7e5b 100644 --- a/src/UI/Handlebars/Helpers/Series.js +++ b/src/UI/Handlebars/Helpers/Series.js @@ -71,6 +71,18 @@ Handlebars.registerHelper('seasonCountHelper', function() { return new Handlebars.SafeString('{0} Seasons'.format(seasonCount)); }); +Handlebars.registerHelper ('truncate', function (str, len) { + if (str && str.length > len && str.length > 0) { + var new_str = str + " "; + new_str = str.substr (0, len); + new_str = str.substr (0, new_str.lastIndexOf(" ")); + new_str = (new_str.length > 0) ? new_str : str.substr (0, len); + + return new Handlebars.SafeString ( new_str +'...' ); + } + return str; +}); + Handlebars.registerHelper('albumCountHelper', function() { var albumCount = this.albumCount; diff --git a/src/UI/Series/Index/Overview/SeriesOverviewItemViewTemplate.hbs b/src/UI/Series/Index/Overview/SeriesOverviewItemViewTemplate.hbs index 4e9785b50..f562800fc 100644 --- a/src/UI/Series/Index/Overview/SeriesOverviewItemViewTemplate.hbs +++ b/src/UI/Series/Index/Overview/SeriesOverviewItemViewTemplate.hbs @@ -20,12 +20,10 @@
-
- -
- {{overview}} -
-
+
+
+ {{truncate overview 600}} +
diff --git a/src/UI/Series/Index/SeriesIndexLayout.js b/src/UI/Series/Index/SeriesIndexLayout.js index 049bd267b..a8ae3d61c 100644 --- a/src/UI/Series/Index/SeriesIndexLayout.js +++ b/src/UI/Series/Index/SeriesIndexLayout.js @@ -20,6 +20,7 @@ require('../../Mixins/backbone.signalr.mixin'); module.exports = Marionette.Layout.extend({ template : 'Series/Index/SeriesIndexLayoutTemplate', + regions : { seriesRegion : '#x-series', toolbar : '#x-toolbar', diff --git a/src/UI/Series/series.less b/src/UI/Series/series.less index c023a7da5..f17229f6f 100644 --- a/src/UI/Series/series.less +++ b/src/UI/Series/series.less @@ -8,6 +8,12 @@ max-width: 100%; } +.truncate { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + .edit-series-modal, .delete-series-modal { overflow : visible;