New: Cache spotify -> musicbrainz mapping

pull/1689/head
ta264 5 years ago committed by Qstick
parent 8280c85b94
commit 4655cbe0c2

@ -120,9 +120,19 @@ namespace NzbDrone.Core.Test.ImportListTests
} }
[Test] [Test]
public void should_not_search_if_album_title_and_album_id() public void should_search_with_lidarr_id_if_album_id_and_no_artist_id()
{ {
WithAlbum(); WithAlbumId();
Subject.Execute(new ImportListSyncCommand());
Mocker.GetMock<ISearchForNewAlbum>()
.Verify(v => v.SearchForNewAlbum($"lidarr:{_importListReports.First().AlbumMusicBrainzId}", null), Times.Once());
}
[Test]
public void should_not_search_if_album_id_and_artist_id()
{
WithArtistId();
WithAlbumId(); WithAlbumId();
Subject.Execute(new ImportListSyncCommand()); Subject.Execute(new ImportListSyncCommand());

@ -0,0 +1,346 @@
using System;
using System.Collections.Generic;
using System.Net;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Cloud;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.ImportLists.Spotify;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.MetadataSource.SkyHook.Resource;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.ImportListTests
{
[TestFixture]
// the base import list class is abstract so use the followed artists one
public class SpotifyMappingFixture : CoreTest<SpotifyFollowedArtists>
{
[SetUp]
public void Setup()
{
Mocker.SetConstant<ILidarrCloudRequestBuilder>(new LidarrCloudRequestBuilder());
Mocker.SetConstant<IMetadataRequestBuilder>(Mocker.Resolve<MetadataRequestBuilder>());
}
[Test]
public void map_artist_should_return_name_if_id_null()
{
var data = new SpotifyImportListItemInfo
{
Artist = "Adele"
};
Subject.MapArtistItem(data);
data.Artist.Should().Be("Adele");
data.ArtistMusicBrainzId.Should().BeNull();
data.Album.Should().BeNull();
data.AlbumMusicBrainzId.Should().BeNull();
}
[Test]
public void map_artist_should_set_id_0_if_no_match()
{
Mocker.GetMock<IHttpClient>()
.Setup(x => x.Get<ArtistResource>(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>((x) => new HttpResponse<ArtistResource>(new HttpResponse(x, new HttpHeader(), new byte[0], HttpStatusCode.NotFound)));
var data = new SpotifyImportListItemInfo
{
Artist = "Adele",
ArtistSpotifyId = "id"
};
Subject.MapArtistItem(data);
data.ArtistMusicBrainzId.Should().Be("0");
}
[Test]
public void map_artist_should_not_update_id_if_http_throws()
{
Mocker.GetMock<IHttpClient>()
.Setup(x => x.Get<ArtistResource>(It.IsAny<HttpRequest>()))
.Throws(new Exception("Dummy exception"));
var data = new SpotifyImportListItemInfo
{
Artist = "Adele",
ArtistSpotifyId = "id"
};
Subject.MapArtistItem(data);
data.ArtistMusicBrainzId.Should().BeNull();
ExceptionVerification.ExpectedErrors(1);
}
[Test]
public void map_artist_should_work()
{
UseRealHttp();
var data = new SpotifyImportListItemInfo
{
Artist = "Adele",
ArtistSpotifyId = "4dpARuHxo51G3z768sgnrY"
};
Subject.MapArtistItem(data);
data.Should().NotBeNull();
data.Artist.Should().Be("Adele");
data.ArtistMusicBrainzId.Should().Be("cc2c9c3c-b7bc-4b8b-84d8-4fbd8779e493");
data.Album.Should().BeNull();
data.AlbumMusicBrainzId.Should().BeNull();
}
[Test]
public void map_album_should_return_name_if_uri_null()
{
var data = new SpotifyImportListItemInfo
{
Album = "25",
Artist = "Adele"
};
Subject.MapAlbumItem(data);
data.Should().NotBeNull();
data.Artist.Should().Be("Adele");
data.ArtistMusicBrainzId.Should().BeNull();
data.Album.Should().Be("25");
data.AlbumMusicBrainzId.Should().BeNull();
}
[Test]
public void map_album_should_set_id_0_if_no_match()
{
Mocker.GetMock<IHttpClient>()
.Setup(x => x.Get<AlbumResource>(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>((x) => new HttpResponse<AlbumResource>(new HttpResponse(x, new HttpHeader(), new byte[0], HttpStatusCode.NotFound)));
var data = new SpotifyImportListItemInfo
{
Album = "25",
AlbumSpotifyId = "id",
Artist = "Adele"
};
Subject.MapAlbumItem(data);
data.AlbumMusicBrainzId.Should().Be("0");
}
[Test]
public void map_album_should_not_update_id_if_http_throws()
{
Mocker.GetMock<IHttpClient>()
.Setup(x => x.Get<AlbumResource>(It.IsAny<HttpRequest>()))
.Throws(new Exception("Dummy exception"));
var data = new SpotifyImportListItemInfo
{
Album = "25",
AlbumSpotifyId = "id",
Artist = "Adele"
};
Subject.MapAlbumItem(data);
data.Should().NotBeNull();
data.Artist.Should().Be("Adele");
data.ArtistMusicBrainzId.Should().BeNull();
data.Album.Should().Be("25");
data.AlbumMusicBrainzId.Should().BeNull();
ExceptionVerification.ExpectedErrors(1);
}
[Test]
public void map_album_should_work()
{
UseRealHttp();
var data = new SpotifyImportListItemInfo
{
Album = "25",
AlbumSpotifyId = "7uwTHXmFa1Ebi5flqBosig",
Artist = "Adele"
};
Subject.MapAlbumItem(data);
data.Should().NotBeNull();
data.Artist.Should().Be("Adele");
data.Album.Should().Be("25");
data.AlbumMusicBrainzId.Should().Be("5537624c-3d2f-4f5c-8099-df916082c85c");
}
[Test]
public void map_spotify_releases_should_only_map_album_id_for_album()
{
var data = new List<SpotifyImportListItemInfo>
{
new SpotifyImportListItemInfo
{
Album = "25",
AlbumSpotifyId = "7uwTHXmFa1Ebi5flqBosig",
Artist = "Adele",
ArtistSpotifyId = "4dpARuHxo51G3z768sgnrY"
}
};
var map = new List<SpotifyMap>
{
new SpotifyMap
{
SpotifyId = "7uwTHXmFa1Ebi5flqBosig",
MusicbrainzId = "5537624c-3d2f-4f5c-8099-df916082c85c"
},
new SpotifyMap
{
SpotifyId = "4dpARuHxo51G3z768sgnrY",
MusicbrainzId = "cc2c9c3c-b7bc-4b8b-84d8-4fbd8779e493"
}
};
Mocker.GetMock<IHttpClient>()
.Setup(x => x.Post<List<SpotifyMap>>(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse<List<SpotifyMap>>(new HttpResponse(r, new HttpHeader(), map.ToJson())));
var result = Subject.MapSpotifyReleases(data);
result[0].AlbumMusicBrainzId.Should().Be("5537624c-3d2f-4f5c-8099-df916082c85c");
result[0].ArtistMusicBrainzId.Should().BeNull();
}
[Test]
public void map_spotify_releases_should_map_artist_id_for_artist()
{
var data = new List<SpotifyImportListItemInfo>
{
new SpotifyImportListItemInfo
{
Artist = "Adele",
ArtistSpotifyId = "4dpARuHxo51G3z768sgnrY"
}
};
var map = new List<SpotifyMap>
{
new SpotifyMap
{
SpotifyId = "7uwTHXmFa1Ebi5flqBosig",
MusicbrainzId = "5537624c-3d2f-4f5c-8099-df916082c85c"
},
new SpotifyMap
{
SpotifyId = "4dpARuHxo51G3z768sgnrY",
MusicbrainzId = "cc2c9c3c-b7bc-4b8b-84d8-4fbd8779e493"
}
};
Mocker.GetMock<IHttpClient>()
.Setup(x => x.Post<List<SpotifyMap>>(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse<List<SpotifyMap>>(new HttpResponse(r, new HttpHeader(), map.ToJson())));
var result = Subject.MapSpotifyReleases(data);
result[0].ArtistMusicBrainzId.Should().Be("cc2c9c3c-b7bc-4b8b-84d8-4fbd8779e493");
}
[Test]
public void map_spotify_releases_should_drop_not_found()
{
var data = new List<SpotifyImportListItemInfo>
{
new SpotifyImportListItemInfo
{
Album = "25",
AlbumSpotifyId = "7uwTHXmFa1Ebi5flqBosig",
Artist = "Adele"
}
};
var map = new List<SpotifyMap>
{
new SpotifyMap
{
SpotifyId = "7uwTHXmFa1Ebi5flqBosig",
MusicbrainzId = "0"
}
};
Mocker.GetMock<IHttpClient>()
.Setup(x => x.Post<List<SpotifyMap>>(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse<List<SpotifyMap>>(new HttpResponse(r, new HttpHeader(), map.ToJson())));
var result = Subject.MapSpotifyReleases(data);
result.Should().BeEmpty();
}
[Test]
public void map_spotify_releases_should_catch_exception_from_api()
{
var data = new List<SpotifyImportListItemInfo>
{
new SpotifyImportListItemInfo
{
Album = "25",
AlbumSpotifyId = "7uwTHXmFa1Ebi5flqBosig",
Artist = "Adele"
}
};
Mocker.GetMock<IHttpClient>()
.Setup(x => x.Post<List<SpotifyMap>>(It.IsAny<HttpRequest>()))
.Throws(new Exception("Dummy exception"));
Mocker.GetMock<IHttpClient>()
.Setup(x => x.Get<AlbumResource>(It.IsAny<HttpRequest>()))
.Throws(new Exception("Dummy exception"));
var result = Subject.MapSpotifyReleases(data);
result.Should().NotBeNull();
ExceptionVerification.ExpectedErrors(2);
}
[Test]
public void map_spotify_releases_should_cope_with_duplicate_spotify_ids()
{
var data = new List<SpotifyImportListItemInfo>
{
new SpotifyImportListItemInfo
{
Album = "25",
AlbumSpotifyId = "7uwTHXmFa1Ebi5flqBosig",
Artist = "Adele"
},
new SpotifyImportListItemInfo
{
Album = "25",
AlbumSpotifyId = "7uwTHXmFa1Ebi5flqBosig",
Artist = "Adele"
}
};
var map = new List<SpotifyMap>
{
new SpotifyMap
{
SpotifyId = "7uwTHXmFa1Ebi5flqBosig",
MusicbrainzId = "5537624c-3d2f-4f5c-8099-df916082c85c"
}
};
Mocker.GetMock<IHttpClient>()
.Setup(x => x.Post<List<SpotifyMap>>(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse<List<SpotifyMap>>(new HttpResponse(r, new HttpHeader(), map.ToJson())));
var result = Subject.MapSpotifyReleases(data);
result.Should().HaveCount(2);
result[0].AlbumMusicBrainzId.Should().Be("5537624c-3d2f-4f5c-8099-df916082c85c");
result[1].AlbumMusicBrainzId.Should().Be("5537624c-3d2f-4f5c-8099-df916082c85c");
}
}
}

@ -104,6 +104,18 @@ namespace NzbDrone.Core.ImportLists
} }
// Map artist ID if we only have album ID
if (report.AlbumMusicBrainzId.IsNotNullOrWhiteSpace() && report.ArtistMusicBrainzId.IsNullOrWhiteSpace())
{
var mappedAlbum = _albumSearchService.SearchForNewAlbum($"lidarr:{report.AlbumMusicBrainzId}", null)
.FirstOrDefault();
if (mappedAlbum == null) continue;
report.Artist = mappedAlbum.ArtistMetadata?.Value?.Name;
report.ArtistMusicBrainzId = mappedAlbum?.ArtistMetadata?.Value?.ForeignArtistId;
}
// Map MBid if we only have a artist name // Map MBid if we only have a artist name
if (report.ArtistMusicBrainzId.IsNullOrWhiteSpace() && report.Artist.IsNotNullOrWhiteSpace()) if (report.ArtistMusicBrainzId.IsNullOrWhiteSpace() && report.Artist.IsNotNullOrWhiteSpace())
{ {

@ -3,6 +3,7 @@ using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using SpotifyAPI.Web; using SpotifyAPI.Web;
@ -18,21 +19,22 @@ namespace NzbDrone.Core.ImportLists.Spotify
public class SpotifyFollowedArtists : SpotifyImportListBase<SpotifyFollowedArtistsSettings> public class SpotifyFollowedArtists : SpotifyImportListBase<SpotifyFollowedArtistsSettings>
{ {
public SpotifyFollowedArtists(ISpotifyProxy spotifyProxy, public SpotifyFollowedArtists(ISpotifyProxy spotifyProxy,
IMetadataRequestBuilder requestBuilder,
IImportListStatusService importListStatusService, IImportListStatusService importListStatusService,
IImportListRepository importListRepository, IImportListRepository importListRepository,
IConfigService configService, IConfigService configService,
IParsingService parsingService, IParsingService parsingService,
IHttpClient httpClient, IHttpClient httpClient,
Logger logger) Logger logger)
: base(spotifyProxy, importListStatusService, importListRepository, configService, parsingService, httpClient, logger) : base(spotifyProxy, requestBuilder, importListStatusService, importListRepository, configService, parsingService, httpClient, logger)
{ {
} }
public override string Name => "Spotify Followed Artists"; public override string Name => "Spotify Followed Artists";
public override IList<ImportListItemInfo> Fetch(SpotifyWebAPI api) public override IList<SpotifyImportListItemInfo> Fetch(SpotifyWebAPI api)
{ {
var result = new List<ImportListItemInfo>(); var result = new List<SpotifyImportListItemInfo>();
var followedArtists = _spotifyProxy.GetFollowedArtists(this, api); var followedArtists = _spotifyProxy.GetFollowedArtists(this, api);
var artists = followedArtists?.Artists; var artists = followedArtists?.Artists;
@ -61,12 +63,14 @@ namespace NzbDrone.Core.ImportLists.Spotify
return result; return result;
} }
private ImportListItemInfo ParseFullArtist(FullArtist artist) private SpotifyImportListItemInfo ParseFullArtist(FullArtist artist)
{ {
if (artist?.Name.IsNotNullOrWhiteSpace() ?? false) if (artist?.Name.IsNotNullOrWhiteSpace() ?? false)
{ {
return new ImportListItemInfo { return new SpotifyImportListItemInfo
{
Artist = artist.Name, Artist = artist.Name,
ArtistSpotifyId = artist.Id
}; };
} }

@ -1,11 +1,16 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Net;
using FluentValidation.Results; using FluentValidation.Results;
using NLog; using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.MetadataSource.SkyHook.Resource;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation; using NzbDrone.Core.Validation;
@ -21,8 +26,10 @@ namespace NzbDrone.Core.ImportLists.Spotify
private IImportListRepository _importListRepository; private IImportListRepository _importListRepository;
protected ISpotifyProxy _spotifyProxy; protected ISpotifyProxy _spotifyProxy;
private readonly IMetadataRequestBuilder _requestBuilder;
protected SpotifyImportListBase(ISpotifyProxy spotifyProxy, protected SpotifyImportListBase(ISpotifyProxy spotifyProxy,
IMetadataRequestBuilder requestBuilder,
IImportListStatusService importListStatusService, IImportListStatusService importListStatusService,
IImportListRepository importListRepository, IImportListRepository importListRepository,
IConfigService configService, IConfigService configService,
@ -34,6 +41,7 @@ namespace NzbDrone.Core.ImportLists.Spotify
_httpClient = httpClient; _httpClient = httpClient;
_importListRepository = importListRepository; _importListRepository = importListRepository;
_spotifyProxy = spotifyProxy; _spotifyProxy = spotifyProxy;
_requestBuilder = requestBuilder;
} }
public override ImportListType ListType => ImportListType.Spotify; public override ImportListType ListType => ImportListType.Spotify;
@ -93,15 +101,20 @@ namespace NzbDrone.Core.ImportLists.Spotify
public override IList<ImportListItemInfo> Fetch() public override IList<ImportListItemInfo> Fetch()
{ {
IList<SpotifyImportListItemInfo> releases;
using (var api = GetApi()) using (var api = GetApi())
{ {
_logger.Debug("Starting spotify import list sync"); _logger.Debug("Starting spotify import list sync");
var releases = Fetch(api); releases = Fetch(api);
return CleanupListItems(releases);
} }
// map to musicbrainz ids
releases = MapSpotifyReleases(releases);
return CleanupListItems(releases);
} }
public abstract IList<ImportListItemInfo> Fetch(SpotifyWebAPI api); public abstract IList<SpotifyImportListItemInfo> Fetch(SpotifyWebAPI api);
protected DateTime ParseSpotifyDate(string date, string precision) protected DateTime ParseSpotifyDate(string date, string precision)
{ {
@ -128,6 +141,156 @@ namespace NzbDrone.Core.ImportLists.Spotify
return DateTime.TryParseExact(date, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime result) ? result : default(DateTime); return DateTime.TryParseExact(date, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime result) ? result : default(DateTime);
} }
public IList<SpotifyImportListItemInfo> MapSpotifyReleases(IList<SpotifyImportListItemInfo> items)
{
// first pass bulk lookup, server won't do search
var spotifyIds = items.Select(x => x.ArtistSpotifyId)
.Concat(items.Select(x => x.AlbumSpotifyId))
.Where(x => x.IsNotNullOrWhiteSpace())
.Distinct();
var httpRequest = _requestBuilder.GetRequestBuilder().Create()
.SetSegment("route", "spotify/lookup")
.Build();
httpRequest.SetContent(spotifyIds.ToJson());
httpRequest.Headers.ContentType = "application/json";
_logger.Trace($"Requesting maps for:\n{spotifyIds.ToJson()}");
Dictionary<string, string> map;
try
{
var httpResponse = _httpClient.Post<List<SpotifyMap>>(httpRequest);
var mapList = httpResponse.Resource;
// Generate a mapping dictionary.
// The API will return 0 to mean it has previously searched and can't find the item.
// null means that it has never been searched before.
map = mapList.Where(x => x.MusicbrainzId.IsNotNullOrWhiteSpace())
.ToDictionary(x => x.SpotifyId, x => x.MusicbrainzId);
}
catch (Exception e)
{
_logger.Error(e);
map = new Dictionary<string, string>();
}
_logger.Trace("Got mapping:\n{0}", map.ToJson());
foreach (var item in items)
{
if (item.AlbumSpotifyId.IsNotNullOrWhiteSpace())
{
if (map.ContainsKey(item.AlbumSpotifyId))
{
item.AlbumMusicBrainzId = map[item.AlbumSpotifyId];
}
else
{
MapAlbumItem(item);
}
}
else if (item.ArtistSpotifyId.IsNotNullOrWhiteSpace())
{
if (map.ContainsKey(item.ArtistSpotifyId))
{
item.ArtistMusicBrainzId = map[item.ArtistSpotifyId];
}
else
{
MapArtistItem(item);
}
}
}
// Strip out items where mapped to not found
return items.Where(x => x.AlbumMusicBrainzId != "0" && x.ArtistMusicBrainzId != "0").ToList();
}
public void MapArtistItem(SpotifyImportListItemInfo item)
{
if (item.ArtistSpotifyId.IsNullOrWhiteSpace())
{
return;
}
var httpRequest = _requestBuilder.GetRequestBuilder().Create()
.SetSegment("route", $"spotify/artist/{item.ArtistSpotifyId}")
.Build();
httpRequest.AllowAutoRedirect = true;
httpRequest.SuppressHttpError = true;
try
{
var response = _httpClient.Get<ArtistResource>(httpRequest);
if (response.HasHttpError)
{
if (response.StatusCode == HttpStatusCode.NotFound)
{
item.ArtistMusicBrainzId = "0";
return;
}
else
{
throw new HttpException(httpRequest, response);
}
}
item.ArtistMusicBrainzId = response.Resource.Id;
}
catch (HttpException e)
{
_logger.Warn(e, "Unable to communicate with LidarrAPI");
}
catch (Exception e)
{
_logger.Error(e);
}
}
public void MapAlbumItem(SpotifyImportListItemInfo item)
{
if (item.AlbumSpotifyId.IsNullOrWhiteSpace())
{
return;
}
var httpRequest = _requestBuilder.GetRequestBuilder().Create()
.SetSegment("route", $"spotify/album/{item.AlbumSpotifyId}")
.Build();
httpRequest.AllowAutoRedirect = true;
httpRequest.SuppressHttpError = true;
try
{
var response = _httpClient.Get<AlbumResource>(httpRequest);
if (response.HasHttpError)
{
if (response.StatusCode == HttpStatusCode.NotFound)
{
item.AlbumMusicBrainzId = "0";
return;
}
else
{
throw new HttpException(httpRequest, response);
}
}
item.ArtistMusicBrainzId = response.Resource.ArtistId;
item.AlbumMusicBrainzId = response.Resource.Id;
}
catch (HttpException e)
{
_logger.Warn(e, "Unable to communicate with LidarrAPI");
}
catch (Exception e)
{
_logger.Error(e);
}
}
protected override void Test(List<ValidationFailure> failures) protected override void Test(List<ValidationFailure> failures)
{ {
failures.AddIfNotNull(TestConnection()); failures.AddIfNotNull(TestConnection());

@ -0,0 +1,15 @@
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.ImportLists.Spotify
{
public class SpotifyImportListItemInfo : ImportListItemInfo
{
public string ArtistSpotifyId { get; set; }
public string AlbumSpotifyId { get; set; }
public override string ToString()
{
return string.Format("[{0}] {1}", ArtistSpotifyId, AlbumSpotifyId);
}
}
}

@ -0,0 +1,8 @@
namespace NzbDrone.Core.ImportLists.Spotify
{
public class SpotifyMap
{
public string SpotifyId { get; set; }
public string MusicbrainzId { get; set; }
}
}

@ -5,8 +5,8 @@ using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation; using NzbDrone.Core.Validation;
using SpotifyAPI.Web; using SpotifyAPI.Web;
using SpotifyAPI.Web.Models; using SpotifyAPI.Web.Models;
@ -16,30 +16,31 @@ namespace NzbDrone.Core.ImportLists.Spotify
public class SpotifyPlaylist : SpotifyImportListBase<SpotifyPlaylistSettings> public class SpotifyPlaylist : SpotifyImportListBase<SpotifyPlaylistSettings>
{ {
public SpotifyPlaylist(ISpotifyProxy spotifyProxy, public SpotifyPlaylist(ISpotifyProxy spotifyProxy,
IMetadataRequestBuilder requestBuilder,
IImportListStatusService importListStatusService, IImportListStatusService importListStatusService,
IImportListRepository importListRepository, IImportListRepository importListRepository,
IConfigService configService, IConfigService configService,
IParsingService parsingService, IParsingService parsingService,
IHttpClient httpClient, IHttpClient httpClient,
Logger logger) Logger logger)
: base(spotifyProxy, importListStatusService, importListRepository, configService, parsingService, httpClient, logger) : base(spotifyProxy, requestBuilder, importListStatusService, importListRepository, configService, parsingService, httpClient, logger)
{ {
} }
public override string Name => "Spotify Playlists"; public override string Name => "Spotify Playlists";
public override IList<ImportListItemInfo> Fetch(SpotifyWebAPI api) public override IList<SpotifyImportListItemInfo> Fetch(SpotifyWebAPI api)
{ {
return Settings.PlaylistIds.SelectMany(x => Fetch(api, x)).ToList(); return Settings.PlaylistIds.SelectMany(x => Fetch(api, x)).ToList();
} }
public IList<ImportListItemInfo> Fetch(SpotifyWebAPI api, string playlistId) public IList<SpotifyImportListItemInfo> Fetch(SpotifyWebAPI api, string playlistId)
{ {
var result = new List<ImportListItemInfo>(); var result = new List<SpotifyImportListItemInfo>();
_logger.Trace($"Processing playlist {playlistId}"); _logger.Trace($"Processing playlist {playlistId}");
var playlistTracks = _spotifyProxy.GetPlaylistTracks(this, api, playlistId, "next, items(track(name, album(name,artists)))"); var playlistTracks = _spotifyProxy.GetPlaylistTracks(this, api, playlistId, "next, items(track(name, artists(id, name), album(id, name, release_date, release_date_precision, artists(id, name))))");
while (true) while (true)
{ {
@ -64,20 +65,23 @@ namespace NzbDrone.Core.ImportLists.Spotify
return result; return result;
} }
private ImportListItemInfo ParsePlaylistTrack(PlaylistTrack playlistTrack) private SpotifyImportListItemInfo ParsePlaylistTrack(PlaylistTrack playlistTrack)
{ {
// From spotify docs: "Note, a track object may be null. This can happen if a track is no longer available." // From spotify docs: "Note, a track object may be null. This can happen if a track is no longer available."
if (playlistTrack?.Track?.Album != null) if (playlistTrack?.Track?.Album != null)
{ {
var album = playlistTrack.Track.Album; var album = playlistTrack.Track.Album;
var albumName = album.Name; var albumName = album.Name;
var artistName = album.Artists?.FirstOrDefault()?.Name ?? playlistTrack.Track?.Artists?.FirstOrDefault()?.Name; var artistName = album.Artists?.FirstOrDefault()?.Name ?? playlistTrack.Track?.Artists?.FirstOrDefault()?.Name;
if (albumName.IsNotNullOrWhiteSpace() && artistName.IsNotNullOrWhiteSpace()) if (albumName.IsNotNullOrWhiteSpace() && artistName.IsNotNullOrWhiteSpace())
{ {
return new ImportListItemInfo { return new SpotifyImportListItemInfo
{
Artist = artistName, Artist = artistName,
Album = albumName, Album = album.Name,
AlbumSpotifyId = album.Id,
ReleaseDate = ParseSpotifyDate(album.ReleaseDate, album.ReleaseDatePrecision) ReleaseDate = ParseSpotifyDate(album.ReleaseDate, album.ReleaseDatePrecision)
}; };
} }

@ -4,6 +4,7 @@ using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using SpotifyAPI.Web; using SpotifyAPI.Web;
@ -19,21 +20,22 @@ namespace NzbDrone.Core.ImportLists.Spotify
public class SpotifySavedAlbums : SpotifyImportListBase<SpotifySavedAlbumsSettings> public class SpotifySavedAlbums : SpotifyImportListBase<SpotifySavedAlbumsSettings>
{ {
public SpotifySavedAlbums(ISpotifyProxy spotifyProxy, public SpotifySavedAlbums(ISpotifyProxy spotifyProxy,
IMetadataRequestBuilder requestBuilder,
IImportListStatusService importListStatusService, IImportListStatusService importListStatusService,
IImportListRepository importListRepository, IImportListRepository importListRepository,
IConfigService configService, IConfigService configService,
IParsingService parsingService, IParsingService parsingService,
IHttpClient httpClient, IHttpClient httpClient,
Logger logger) Logger logger)
: base(spotifyProxy, importListStatusService, importListRepository, configService, parsingService, httpClient, logger) : base(spotifyProxy, requestBuilder, importListStatusService, importListRepository, configService, parsingService, httpClient, logger)
{ {
} }
public override string Name => "Spotify Saved Albums"; public override string Name => "Spotify Saved Albums";
public override IList<ImportListItemInfo> Fetch(SpotifyWebAPI api) public override IList<SpotifyImportListItemInfo> Fetch(SpotifyWebAPI api)
{ {
var result = new List<ImportListItemInfo>(); var result = new List<SpotifyImportListItemInfo>();
var savedAlbums = _spotifyProxy.GetSavedAlbums(this, api); var savedAlbums = _spotifyProxy.GetSavedAlbums(this, api);
@ -62,7 +64,7 @@ namespace NzbDrone.Core.ImportLists.Spotify
return result; return result;
} }
private ImportListItemInfo ParseSavedAlbum(SavedAlbum savedAlbum) private SpotifyImportListItemInfo ParseSavedAlbum(SavedAlbum savedAlbum)
{ {
var artistName = savedAlbum?.Album?.Artists?.FirstOrDefault()?.Name; var artistName = savedAlbum?.Album?.Artists?.FirstOrDefault()?.Name;
var albumName = savedAlbum?.Album?.Name; var albumName = savedAlbum?.Album?.Name;
@ -70,9 +72,11 @@ namespace NzbDrone.Core.ImportLists.Spotify
if (artistName.IsNotNullOrWhiteSpace() && albumName.IsNotNullOrWhiteSpace()) if (artistName.IsNotNullOrWhiteSpace() && albumName.IsNotNullOrWhiteSpace())
{ {
return new ImportListItemInfo { return new SpotifyImportListItemInfo
{
Artist = artistName, Artist = artistName,
Album = albumName, Album = albumName,
AlbumSpotifyId = savedAlbum?.Album?.Id,
ReleaseDate = ParseSpotifyDate(savedAlbum?.Album?.ReleaseDate, savedAlbum?.Album?.ReleaseDatePrecision) ReleaseDate = ParseSpotifyDate(savedAlbum?.Album?.ReleaseDate, savedAlbum?.Album?.ReleaseDatePrecision)
}; };
} }

Loading…
Cancel
Save