mirror of
https://github.com/lidarr/Lidarr
synced 2025-03-13 07:23:14 +00:00
New: Cache spotify -> musicbrainz mapping
This commit is contained in:
parent
b050c73d87
commit
6a9887f7e2
9 changed files with 590 additions and 24 deletions
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract IList<ImportListItemInfo> Fetch(SpotifyWebAPI api);
|
// map to musicbrainz ids
|
||||||
|
releases = MapSpotifyReleases(releases);
|
||||||
|
|
||||||
|
return CleanupListItems(releases);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
src/NzbDrone.Core/ImportLists/Spotify/SpotifyMap.cs
Normal file
8
src/NzbDrone.Core/ImportLists/Spotify/SpotifyMap.cs
Normal file
|
@ -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…
Add table
Reference in a new issue