mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-10 23:33:38 -07:00
Fixed: NRE importing Spotify saved albums / followed artists (#962)
This commit is contained in:
parent
e52013e85a
commit
1b72d9b60f
8 changed files with 834 additions and 109 deletions
|
@ -0,0 +1,173 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.ImportLists.Spotify;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using SpotifyAPI.Web;
|
||||||
|
using SpotifyAPI.Web.Models;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.ImportListTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class SpotifyFollowedArtistsFixture : CoreTest<SpotifyFollowedArtists>
|
||||||
|
{
|
||||||
|
// placeholder, we don't use real API
|
||||||
|
private readonly SpotifyWebAPI api = null;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_throw_if_followed_is_null()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<ISpotifyProxy>().
|
||||||
|
Setup(x => x.GetFollowedArtists(It.IsAny<SpotifyFollowedArtists>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>()))
|
||||||
|
.Returns(default(FollowedArtists));
|
||||||
|
|
||||||
|
var result = Subject.Fetch(api);
|
||||||
|
|
||||||
|
result.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_throw_if_followed_artists_is_null()
|
||||||
|
{
|
||||||
|
var followed = new FollowedArtists {
|
||||||
|
Artists = null
|
||||||
|
};
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>().
|
||||||
|
Setup(x => x.GetFollowedArtists(It.IsAny<SpotifyFollowedArtists>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>()))
|
||||||
|
.Returns(followed);
|
||||||
|
|
||||||
|
var result = Subject.Fetch(api);
|
||||||
|
|
||||||
|
result.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_throw_if_followed_artist_items_is_null()
|
||||||
|
{
|
||||||
|
var followed = new FollowedArtists {
|
||||||
|
Artists = new CursorPaging<FullArtist> {
|
||||||
|
Items = null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>().
|
||||||
|
Setup(x => x.GetFollowedArtists(It.IsAny<SpotifyFollowedArtists>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>()))
|
||||||
|
.Returns(followed);
|
||||||
|
|
||||||
|
var result = Subject.Fetch(api);
|
||||||
|
|
||||||
|
result.Should().BeEmpty();
|
||||||
|
Subject.Fetch(api);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_throw_if_artist_is_null()
|
||||||
|
{
|
||||||
|
var followed = new FollowedArtists {
|
||||||
|
Artists = new CursorPaging<FullArtist> {
|
||||||
|
Items = new List<FullArtist> {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>().
|
||||||
|
Setup(x => x.GetFollowedArtists(It.IsAny<SpotifyFollowedArtists>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>()))
|
||||||
|
.Returns(followed);
|
||||||
|
|
||||||
|
var result = Subject.Fetch(api);
|
||||||
|
|
||||||
|
result.Should().BeEmpty();
|
||||||
|
Subject.Fetch(api);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_parse_followed_artist()
|
||||||
|
{
|
||||||
|
var followed = new FollowedArtists {
|
||||||
|
Artists = new CursorPaging<FullArtist> {
|
||||||
|
Items = new List<FullArtist> {
|
||||||
|
new FullArtist {
|
||||||
|
Name = "artist"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>().
|
||||||
|
Setup(x => x.GetFollowedArtists(It.IsAny<SpotifyFollowedArtists>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>()))
|
||||||
|
.Returns(followed);
|
||||||
|
|
||||||
|
var result = Subject.Fetch(api);
|
||||||
|
|
||||||
|
result.Should().HaveCount(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_throw_if_get_next_page_returns_null()
|
||||||
|
{
|
||||||
|
var followed = new FollowedArtists {
|
||||||
|
Artists = new CursorPaging<FullArtist> {
|
||||||
|
Items = new List<FullArtist> {
|
||||||
|
new FullArtist {
|
||||||
|
Name = "artist"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Next = "DummyToMakeHasNextTrue"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>().
|
||||||
|
Setup(x => x.GetFollowedArtists(It.IsAny<SpotifyFollowedArtists>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>()))
|
||||||
|
.Returns(followed);
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>()
|
||||||
|
.Setup(x => x.GetNextPage(It.IsAny<SpotifyFollowedArtists>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>(),
|
||||||
|
It.IsAny<CursorPaging<FullArtist>>()))
|
||||||
|
.Returns(default(CursorPaging<FullArtist>));
|
||||||
|
|
||||||
|
var result = Subject.Fetch(api);
|
||||||
|
|
||||||
|
result.Should().HaveCount(1);
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>()
|
||||||
|
.Verify(v => v.GetNextPage(It.IsAny<SpotifyFollowedArtists>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>(),
|
||||||
|
It.IsAny<CursorPaging<FullArtist>>()),
|
||||||
|
Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(null)]
|
||||||
|
[TestCase("")]
|
||||||
|
public void should_skip_bad_artist_names(string name)
|
||||||
|
{
|
||||||
|
var followed = new FollowedArtists {
|
||||||
|
Artists = new CursorPaging<FullArtist> {
|
||||||
|
Items = new List<FullArtist> {
|
||||||
|
new FullArtist {
|
||||||
|
Name = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>().
|
||||||
|
Setup(x => x.GetFollowedArtists(It.IsAny<SpotifyFollowedArtists>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>()))
|
||||||
|
.Returns(followed);
|
||||||
|
|
||||||
|
var result = Subject.Fetch(api);
|
||||||
|
|
||||||
|
result.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,239 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.ImportLists.Spotify;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using SpotifyAPI.Web;
|
||||||
|
using SpotifyAPI.Web.Models;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.ImportListTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class SpotifyPlaylistFixture : CoreTest<SpotifyPlaylist>
|
||||||
|
{
|
||||||
|
// placeholder, we don't use real API
|
||||||
|
private readonly SpotifyWebAPI api = null;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_throw_if_playlist_tracks_is_null()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<ISpotifyProxy>().
|
||||||
|
Setup(x => x.GetPlaylistTracks(It.IsAny<SpotifyPlaylist>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>()))
|
||||||
|
.Returns(default(Paging<PlaylistTrack>));
|
||||||
|
|
||||||
|
var result = Subject.Fetch(api, "playlistid");
|
||||||
|
|
||||||
|
result.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_throw_if_playlist_tracks_items_is_null()
|
||||||
|
{
|
||||||
|
var playlistTracks = new Paging<PlaylistTrack> {
|
||||||
|
Items = null
|
||||||
|
};
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>().
|
||||||
|
Setup(x => x.GetPlaylistTracks(It.IsAny<SpotifyPlaylist>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>()))
|
||||||
|
.Returns(playlistTracks);
|
||||||
|
|
||||||
|
var result = Subject.Fetch(api, "playlistid");
|
||||||
|
|
||||||
|
result.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_throw_if_playlist_track_is_null()
|
||||||
|
{
|
||||||
|
var playlistTracks = new Paging<PlaylistTrack> {
|
||||||
|
Items = new List<PlaylistTrack> {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>().
|
||||||
|
Setup(x => x.GetPlaylistTracks(It.IsAny<SpotifyPlaylist>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>()))
|
||||||
|
.Returns(playlistTracks);
|
||||||
|
|
||||||
|
var result = Subject.Fetch(api, "playlistid");
|
||||||
|
|
||||||
|
result.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_use_album_artist_when_it_exists()
|
||||||
|
{
|
||||||
|
var playlistTracks = new Paging<PlaylistTrack> {
|
||||||
|
Items = new List<PlaylistTrack> {
|
||||||
|
new PlaylistTrack {
|
||||||
|
Track = new FullTrack {
|
||||||
|
Album = new SimpleAlbum {
|
||||||
|
Name = "Album",
|
||||||
|
Artists = new List<SimpleArtist> {
|
||||||
|
new SimpleArtist {
|
||||||
|
Name = "AlbumArtist"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Artists = new List<SimpleArtist> {
|
||||||
|
new SimpleArtist {
|
||||||
|
Name = "TrackArtist"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>().
|
||||||
|
Setup(x => x.GetPlaylistTracks(It.IsAny<SpotifyPlaylist>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>()))
|
||||||
|
.Returns(playlistTracks);
|
||||||
|
|
||||||
|
var result = Subject.Fetch(api, "playlistid");
|
||||||
|
|
||||||
|
result.Should().HaveCount(1);
|
||||||
|
result[0].Artist.Should().Be("AlbumArtist");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_fall_back_to_track_artist_if_album_artist_missing()
|
||||||
|
{
|
||||||
|
var playlistTracks = new Paging<PlaylistTrack> {
|
||||||
|
Items = new List<PlaylistTrack> {
|
||||||
|
new PlaylistTrack {
|
||||||
|
Track = new FullTrack {
|
||||||
|
Album = new SimpleAlbum {
|
||||||
|
Name = "Album",
|
||||||
|
Artists = new List<SimpleArtist> {
|
||||||
|
new SimpleArtist {
|
||||||
|
Name = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Artists = new List<SimpleArtist> {
|
||||||
|
new SimpleArtist {
|
||||||
|
Name = "TrackArtist"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>().
|
||||||
|
Setup(x => x.GetPlaylistTracks(It.IsAny<SpotifyPlaylist>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>()))
|
||||||
|
.Returns(playlistTracks);
|
||||||
|
|
||||||
|
var result = Subject.Fetch(api, "playlistid");
|
||||||
|
|
||||||
|
result.Should().HaveCount(1);
|
||||||
|
result[0].Artist.Should().Be("TrackArtist");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[TestCase(null, null, "Album")]
|
||||||
|
[TestCase("AlbumArtist", null, null)]
|
||||||
|
[TestCase(null, "TrackArtist", null)]
|
||||||
|
public void should_skip_bad_artist_or_album_names(string albumArtistName, string trackArtistName, string albumName)
|
||||||
|
{
|
||||||
|
var playlistTracks = new Paging<PlaylistTrack> {
|
||||||
|
Items = new List<PlaylistTrack> {
|
||||||
|
new PlaylistTrack {
|
||||||
|
Track = new FullTrack {
|
||||||
|
Album = new SimpleAlbum {
|
||||||
|
Name = albumName,
|
||||||
|
Artists = new List<SimpleArtist> {
|
||||||
|
new SimpleArtist {
|
||||||
|
Name = albumArtistName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Artists = new List<SimpleArtist> {
|
||||||
|
new SimpleArtist {
|
||||||
|
Name = trackArtistName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>().
|
||||||
|
Setup(x => x.GetPlaylistTracks(It.IsAny<SpotifyPlaylist>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>()))
|
||||||
|
.Returns(playlistTracks);
|
||||||
|
|
||||||
|
var result = Subject.Fetch(api, "playlistid");
|
||||||
|
|
||||||
|
result.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_throw_if_get_next_page_returns_null()
|
||||||
|
{
|
||||||
|
var playlistTracks = new Paging<PlaylistTrack> {
|
||||||
|
Items = new List<PlaylistTrack> {
|
||||||
|
new PlaylistTrack {
|
||||||
|
Track = new FullTrack {
|
||||||
|
Album = new SimpleAlbum {
|
||||||
|
Name = "Album",
|
||||||
|
Artists = new List<SimpleArtist> {
|
||||||
|
new SimpleArtist {
|
||||||
|
Name = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Artists = new List<SimpleArtist> {
|
||||||
|
new SimpleArtist {
|
||||||
|
Name = "TrackArtist"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Next = "DummyToMakeHasNextTrue"
|
||||||
|
};
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>().
|
||||||
|
Setup(x => x.GetPlaylistTracks(It.IsAny<SpotifyPlaylist>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>()))
|
||||||
|
.Returns(playlistTracks);
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>()
|
||||||
|
.Setup(x => x.GetNextPage(It.IsAny<SpotifyFollowedArtists>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>(),
|
||||||
|
It.IsAny<Paging<PlaylistTrack>>()))
|
||||||
|
.Returns(default(Paging<PlaylistTrack>));
|
||||||
|
|
||||||
|
var result = Subject.Fetch(api, "playlistid");
|
||||||
|
|
||||||
|
result.Should().HaveCount(1);
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>()
|
||||||
|
.Verify(x => x.GetNextPage(It.IsAny<SpotifyPlaylist>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>(),
|
||||||
|
It.IsAny<Paging<PlaylistTrack>>()),
|
||||||
|
Times.Once());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,166 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.ImportLists.Spotify;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using SpotifyAPI.Web;
|
||||||
|
using SpotifyAPI.Web.Models;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.ImportListTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class SpotifySavedAlbumsFixture : CoreTest<SpotifySavedAlbums>
|
||||||
|
{
|
||||||
|
// placeholder, we don't use real API
|
||||||
|
private readonly SpotifyWebAPI api = null;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_throw_if_saved_albums_is_null()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<ISpotifyProxy>().
|
||||||
|
Setup(x => x.GetSavedAlbums(It.IsAny<SpotifySavedAlbums>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>()))
|
||||||
|
.Returns(default(Paging<SavedAlbum>));
|
||||||
|
|
||||||
|
var result = Subject.Fetch(api);
|
||||||
|
|
||||||
|
result.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_throw_if_saved_album_items_is_null()
|
||||||
|
{
|
||||||
|
var savedAlbums = new Paging<SavedAlbum> {
|
||||||
|
Items = null
|
||||||
|
};
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>().
|
||||||
|
Setup(x => x.GetSavedAlbums(It.IsAny<SpotifySavedAlbums>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>()))
|
||||||
|
.Returns(savedAlbums);
|
||||||
|
|
||||||
|
var result = Subject.Fetch(api);
|
||||||
|
|
||||||
|
result.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_throw_if_saved_album_is_null()
|
||||||
|
{
|
||||||
|
var savedAlbums = new Paging<SavedAlbum> {
|
||||||
|
Items = new List<SavedAlbum> {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>().
|
||||||
|
Setup(x => x.GetSavedAlbums(It.IsAny<SpotifySavedAlbums>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>()))
|
||||||
|
.Returns(savedAlbums);
|
||||||
|
|
||||||
|
var result = Subject.Fetch(api);
|
||||||
|
|
||||||
|
result.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("Artist", "Album")]
|
||||||
|
public void should_parse_saved_album(string artistName, string albumName)
|
||||||
|
{
|
||||||
|
var savedAlbums = new Paging<SavedAlbum> {
|
||||||
|
Items = new List<SavedAlbum> {
|
||||||
|
new SavedAlbum {
|
||||||
|
Album = new FullAlbum {
|
||||||
|
Name = albumName,
|
||||||
|
Artists = new List<SimpleArtist> {
|
||||||
|
new SimpleArtist {
|
||||||
|
Name = artistName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>().
|
||||||
|
Setup(x => x.GetSavedAlbums(It.IsAny<SpotifySavedAlbums>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>()))
|
||||||
|
.Returns(savedAlbums);
|
||||||
|
|
||||||
|
var result = Subject.Fetch(api);
|
||||||
|
|
||||||
|
result.Should().HaveCount(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_throw_if_get_next_page_returns_null()
|
||||||
|
{
|
||||||
|
var savedAlbums = new Paging<SavedAlbum> {
|
||||||
|
Items = new List<SavedAlbum> {
|
||||||
|
new SavedAlbum {
|
||||||
|
Album = new FullAlbum {
|
||||||
|
Name = "Album",
|
||||||
|
Artists = new List<SimpleArtist> {
|
||||||
|
new SimpleArtist {
|
||||||
|
Name = "Artist"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Next = "DummyToMakeHasNextTrue"
|
||||||
|
};
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>().
|
||||||
|
Setup(x => x.GetSavedAlbums(It.IsAny<SpotifySavedAlbums>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>()))
|
||||||
|
.Returns(savedAlbums);
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>()
|
||||||
|
.Setup(x => x.GetNextPage(It.IsAny<SpotifyFollowedArtists>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>(),
|
||||||
|
It.IsAny<Paging<SavedAlbum>>()))
|
||||||
|
.Returns(default(Paging<SavedAlbum>));
|
||||||
|
|
||||||
|
var result = Subject.Fetch(api);
|
||||||
|
|
||||||
|
result.Should().HaveCount(1);
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>()
|
||||||
|
.Verify(x => x.GetNextPage(It.IsAny<SpotifySavedAlbums>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>(),
|
||||||
|
It.IsAny<Paging<SavedAlbum>>()),
|
||||||
|
Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(null, "Album")]
|
||||||
|
[TestCase("Artist", null)]
|
||||||
|
[TestCase(null, null)]
|
||||||
|
public void should_skip_bad_artist_or_album_names(string artistName, string albumName)
|
||||||
|
{
|
||||||
|
var savedAlbums = new Paging<SavedAlbum> {
|
||||||
|
Items = new List<SavedAlbum> {
|
||||||
|
new SavedAlbum {
|
||||||
|
Album = new FullAlbum {
|
||||||
|
Name = albumName,
|
||||||
|
Artists = new List<SimpleArtist> {
|
||||||
|
new SimpleArtist {
|
||||||
|
Name = artistName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Mocker.GetMock<ISpotifyProxy>().
|
||||||
|
Setup(x => x.GetSavedAlbums(It.IsAny<SpotifySavedAlbums>(),
|
||||||
|
It.IsAny<SpotifyWebAPI>()))
|
||||||
|
.Returns(savedAlbums);
|
||||||
|
|
||||||
|
var result = Subject.Fetch(api);
|
||||||
|
|
||||||
|
result.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using SpotifyAPI.Web;
|
using SpotifyAPI.Web;
|
||||||
using SpotifyAPI.Web.Enums;
|
using SpotifyAPI.Web.Models;
|
||||||
|
|
||||||
namespace NzbDrone.Core.ImportLists.Spotify
|
namespace NzbDrone.Core.ImportLists.Spotify
|
||||||
{
|
{
|
||||||
|
@ -17,13 +17,14 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
||||||
|
|
||||||
public class SpotifyFollowedArtists : SpotifyImportListBase<SpotifyFollowedArtistsSettings>
|
public class SpotifyFollowedArtists : SpotifyImportListBase<SpotifyFollowedArtistsSettings>
|
||||||
{
|
{
|
||||||
public SpotifyFollowedArtists(IImportListStatusService importListStatusService,
|
public SpotifyFollowedArtists(ISpotifyProxy spotifyProxy,
|
||||||
|
IImportListStatusService importListStatusService,
|
||||||
IImportListRepository importListRepository,
|
IImportListRepository importListRepository,
|
||||||
IConfigService configService,
|
IConfigService configService,
|
||||||
IParsingService parsingService,
|
IParsingService parsingService,
|
||||||
HttpClient httpClient,
|
IHttpClient httpClient,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
: base(importListStatusService, importListRepository, configService, parsingService, httpClient, logger)
|
: base(spotifyProxy, importListStatusService, importListRepository, configService, parsingService, httpClient, logger)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,27 +33,43 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
||||||
public override IList<ImportListItemInfo> Fetch(SpotifyWebAPI api)
|
public override IList<ImportListItemInfo> Fetch(SpotifyWebAPI api)
|
||||||
{
|
{
|
||||||
var result = new List<ImportListItemInfo>();
|
var result = new List<ImportListItemInfo>();
|
||||||
|
|
||||||
var followed = Execute(api, (x) => x.GetFollowedArtists(FollowType.Artist, 50));
|
var followedArtists = _spotifyProxy.GetFollowedArtists(this, api);
|
||||||
var artists = followed.Artists;
|
var artists = followedArtists?.Artists;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
if (artists?.Items == null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var artist in artists.Items)
|
foreach (var artist in artists.Items)
|
||||||
{
|
{
|
||||||
if (artist.Name.IsNotNullOrWhiteSpace())
|
result.AddIfNotNull(ParseFullArtist(artist));
|
||||||
{
|
|
||||||
result.AddIfNotNull(new ImportListItemInfo
|
|
||||||
{
|
|
||||||
Artist = artist.Name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!artists.HasNext())
|
if (!artists.HasNext())
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
artists = Execute(api, (x) => x.GetNextPage(artists));
|
}
|
||||||
|
|
||||||
|
artists = _spotifyProxy.GetNextPage(this, api, artists);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ImportListItemInfo ParseFullArtist(FullArtist artist)
|
||||||
|
{
|
||||||
|
if (artist?.Name.IsNotNullOrWhiteSpace() ?? false)
|
||||||
|
{
|
||||||
|
return new ImportListItemInfo {
|
||||||
|
Artist = artist.Name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,21 +20,27 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
||||||
private IHttpClient _httpClient;
|
private IHttpClient _httpClient;
|
||||||
private IImportListRepository _importListRepository;
|
private IImportListRepository _importListRepository;
|
||||||
|
|
||||||
public SpotifyImportListBase(IImportListStatusService importListStatusService,
|
protected ISpotifyProxy _spotifyProxy;
|
||||||
IImportListRepository importListRepository,
|
|
||||||
IConfigService configService,
|
protected SpotifyImportListBase(ISpotifyProxy spotifyProxy,
|
||||||
IParsingService parsingService,
|
IImportListStatusService importListStatusService,
|
||||||
HttpClient httpClient,
|
IImportListRepository importListRepository,
|
||||||
Logger logger)
|
IConfigService configService,
|
||||||
|
IParsingService parsingService,
|
||||||
|
IHttpClient httpClient,
|
||||||
|
Logger logger)
|
||||||
: base(importListStatusService, configService, parsingService, logger)
|
: base(importListStatusService, configService, parsingService, logger)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
_importListRepository = importListRepository;
|
_importListRepository = importListRepository;
|
||||||
|
_spotifyProxy = spotifyProxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ImportListType ListType => ImportListType.Spotify;
|
public override ImportListType ListType => ImportListType.Spotify;
|
||||||
|
|
||||||
private void RefreshToken()
|
public string AccessToken => Settings.AccessToken;
|
||||||
|
|
||||||
|
public void RefreshToken()
|
||||||
{
|
{
|
||||||
_logger.Trace("Refreshing Token");
|
_logger.Trace("Refreshing Token");
|
||||||
|
|
||||||
|
@ -68,7 +74,7 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SpotifyWebAPI GetApi()
|
public SpotifyWebAPI GetApi()
|
||||||
{
|
{
|
||||||
Settings.Validate().Filter("AccessToken", "RefreshToken").ThrowOnError();
|
Settings.Validate().Filter("AccessToken", "RefreshToken").ThrowOnError();
|
||||||
_logger.Trace($"Access token expires at {Settings.Expires}");
|
_logger.Trace($"Access token expires at {Settings.Expires}");
|
||||||
|
@ -85,35 +91,6 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected T Execute<T>(SpotifyWebAPI api, Func<SpotifyWebAPI, T> method, bool allowReauth = true) where T : BasicModel
|
|
||||||
{
|
|
||||||
T result = method(api);
|
|
||||||
if (result.HasError())
|
|
||||||
{
|
|
||||||
// If unauthorized, refresh token and try again
|
|
||||||
if (result.Error.Status == 401)
|
|
||||||
{
|
|
||||||
if (allowReauth)
|
|
||||||
{
|
|
||||||
_logger.Debug("Spotify authorization error, refreshing token and retrying");
|
|
||||||
RefreshToken();
|
|
||||||
api.AccessToken = Settings.AccessToken;
|
|
||||||
return Execute(api, method, false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new SpotifyAuthorizationException(result.Error.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new SpotifyException("[{0}] {1}", result.Error.Status, result.Error.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IList<ImportListItemInfo> Fetch()
|
public override IList<ImportListItemInfo> Fetch()
|
||||||
{
|
{
|
||||||
using (var api = GetApi())
|
using (var api = GetApi())
|
||||||
|
@ -162,7 +139,7 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
||||||
{
|
{
|
||||||
using (var api = GetApi())
|
using (var api = GetApi())
|
||||||
{
|
{
|
||||||
var profile = Execute(api, (x) => x.GetPrivateProfile());
|
var profile = _spotifyProxy.GetPrivateProfile(this, api);
|
||||||
_logger.Debug($"Connected to spotify profile {profile.DisplayName} [{profile.Id}]");
|
_logger.Debug($"Connected to spotify profile {profile.DisplayName} [{profile.Id}]");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,60 +15,77 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
||||||
{
|
{
|
||||||
public class SpotifyPlaylist : SpotifyImportListBase<SpotifyPlaylistSettings>
|
public class SpotifyPlaylist : SpotifyImportListBase<SpotifyPlaylistSettings>
|
||||||
{
|
{
|
||||||
public SpotifyPlaylist(IImportListStatusService importListStatusService,
|
public SpotifyPlaylist(ISpotifyProxy spotifyProxy,
|
||||||
|
IImportListStatusService importListStatusService,
|
||||||
IImportListRepository importListRepository,
|
IImportListRepository importListRepository,
|
||||||
IConfigService configService,
|
IConfigService configService,
|
||||||
IParsingService parsingService,
|
IParsingService parsingService,
|
||||||
HttpClient httpClient,
|
IHttpClient httpClient,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
: base(importListStatusService, importListRepository, configService, parsingService, httpClient, logger)
|
: base(spotifyProxy, 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<ImportListItemInfo> Fetch(SpotifyWebAPI api)
|
||||||
|
{
|
||||||
|
return Settings.PlaylistIds.SelectMany(x => Fetch(api, x)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IList<ImportListItemInfo> Fetch(SpotifyWebAPI api, string playlistId)
|
||||||
{
|
{
|
||||||
var result = new List<ImportListItemInfo>();
|
var result = new List<ImportListItemInfo>();
|
||||||
|
|
||||||
foreach (var id in Settings.PlaylistIds)
|
_logger.Trace($"Processing playlist {playlistId}");
|
||||||
|
|
||||||
|
var playlistTracks = _spotifyProxy.GetPlaylistTracks(this, api, playlistId, "next, items(track(name, album(name,artists)))");
|
||||||
|
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
_logger.Trace($"Processing playlist {id}");
|
if (playlistTracks?.Items == null)
|
||||||
|
|
||||||
var playlistTracks = Execute(api, (x) => x.GetPlaylistTracks(id, fields: "next, items(track(name, album(name,artists)))"));
|
|
||||||
while (true)
|
|
||||||
{
|
{
|
||||||
foreach (var track in playlistTracks.Items)
|
return result;
|
||||||
{
|
|
||||||
var fullTrack = track.Track;
|
|
||||||
// From spotify docs: "Note, a track object may be null. This can happen if a track is no longer available."
|
|
||||||
if (fullTrack != null)
|
|
||||||
{
|
|
||||||
var album = fullTrack.Album?.Name;
|
|
||||||
var artist = fullTrack.Album?.Artists?.FirstOrDefault()?.Name ?? fullTrack.Artists.FirstOrDefault()?.Name;
|
|
||||||
|
|
||||||
if (album.IsNotNullOrWhiteSpace() && artist.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
result.AddIfNotNull(new ImportListItemInfo
|
|
||||||
{
|
|
||||||
Artist = artist,
|
|
||||||
Album = album,
|
|
||||||
ReleaseDate = ParseSpotifyDate(fullTrack.Album.ReleaseDate, fullTrack.Album.ReleaseDatePrecision)
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!playlistTracks.HasNextPage())
|
|
||||||
break;
|
|
||||||
playlistTracks = Execute(api, (x) => x.GetNextPage(playlistTracks));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var playlistTrack in playlistTracks.Items)
|
||||||
|
{
|
||||||
|
result.AddIfNotNull(ParsePlaylistTrack(playlistTrack));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!playlistTracks.HasNextPage())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
playlistTracks = _spotifyProxy.GetNextPage(this, api, playlistTracks);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ImportListItemInfo ParsePlaylistTrack(PlaylistTrack playlistTrack)
|
||||||
|
{
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
var album = playlistTrack.Track.Album;
|
||||||
|
var albumName = album.Name;
|
||||||
|
var artistName = album.Artists?.FirstOrDefault()?.Name ?? playlistTrack.Track?.Artists?.FirstOrDefault()?.Name;
|
||||||
|
|
||||||
|
if (albumName.IsNotNullOrWhiteSpace() && artistName.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return new ImportListItemInfo {
|
||||||
|
Artist = artistName,
|
||||||
|
Album = albumName,
|
||||||
|
ReleaseDate = ParseSpotifyDate(album.ReleaseDate, album.ReleaseDatePrecision)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public override object RequestAction(string action, IDictionary<string, string> query)
|
public override object RequestAction(string action, IDictionary<string, string> query)
|
||||||
{
|
{
|
||||||
if (action == "getPlaylists")
|
if (action == "getPlaylists")
|
||||||
|
@ -87,18 +104,26 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var profile = Execute(api, (x) => x.GetPrivateProfile());
|
var profile = _spotifyProxy.GetPrivateProfile(this, api);
|
||||||
var playlistPage = Execute(api, (x) => x.GetUserPlaylists(profile.Id));
|
var playlistPage = _spotifyProxy.GetUserPlaylists(this, api, profile.Id);
|
||||||
_logger.Trace($"Got {playlistPage.Total} playlists");
|
_logger.Trace($"Got {playlistPage.Total} playlists");
|
||||||
|
|
||||||
var playlists = new List<SimplePlaylist>(playlistPage.Total);
|
var playlists = new List<SimplePlaylist>(playlistPage.Total);
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
if (playlistPage == null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
playlists.AddRange(playlistPage.Items);
|
playlists.AddRange(playlistPage.Items);
|
||||||
|
|
||||||
if (!playlistPage.HasNextPage())
|
if (!playlistPage.HasNextPage())
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
playlistPage = Execute(api, (x) => x.GetNextPage(playlistPage));
|
}
|
||||||
|
|
||||||
|
playlistPage = _spotifyProxy.GetNextPage(this, api, playlistPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new
|
return new
|
||||||
|
|
109
src/NzbDrone.Core/ImportLists/Spotify/SpotifyProxy.cs
Normal file
109
src/NzbDrone.Core/ImportLists/Spotify/SpotifyProxy.cs
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
using System;
|
||||||
|
using NLog;
|
||||||
|
using SpotifyAPI.Web;
|
||||||
|
using SpotifyAPI.Web.Enums;
|
||||||
|
using SpotifyAPI.Web.Models;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.ImportLists.Spotify
|
||||||
|
{
|
||||||
|
public interface ISpotifyProxy
|
||||||
|
{
|
||||||
|
PrivateProfile GetPrivateProfile<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api)
|
||||||
|
where TSettings : SpotifySettingsBase<TSettings>, new();
|
||||||
|
Paging<SimplePlaylist> GetUserPlaylists<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api, string id)
|
||||||
|
where TSettings : SpotifySettingsBase<TSettings>, new();
|
||||||
|
FollowedArtists GetFollowedArtists<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api)
|
||||||
|
where TSettings : SpotifySettingsBase<TSettings>, new();
|
||||||
|
Paging<SavedAlbum> GetSavedAlbums<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api)
|
||||||
|
where TSettings : SpotifySettingsBase<TSettings>, new();
|
||||||
|
Paging<PlaylistTrack> GetPlaylistTracks<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api, string id, string fields)
|
||||||
|
where TSettings : SpotifySettingsBase<TSettings>, new();
|
||||||
|
Paging<T> GetNextPage<T, TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api, Paging<T> item)
|
||||||
|
where TSettings : SpotifySettingsBase<TSettings>, new();
|
||||||
|
CursorPaging<T> GetNextPage<T, TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api, CursorPaging<T> item)
|
||||||
|
where TSettings : SpotifySettingsBase<TSettings>, new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SpotifyProxy : ISpotifyProxy
|
||||||
|
{
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public SpotifyProxy(Logger logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrivateProfile GetPrivateProfile<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api)
|
||||||
|
where TSettings : SpotifySettingsBase<TSettings>, new()
|
||||||
|
{
|
||||||
|
return Execute(list, api, x => x.GetPrivateProfile());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Paging<SimplePlaylist> GetUserPlaylists<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api, string id)
|
||||||
|
where TSettings : SpotifySettingsBase<TSettings>, new()
|
||||||
|
{
|
||||||
|
return Execute(list, api, x => x.GetUserPlaylists(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public FollowedArtists GetFollowedArtists<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api)
|
||||||
|
where TSettings : SpotifySettingsBase<TSettings>, new()
|
||||||
|
{
|
||||||
|
return Execute(list, api, x => x.GetFollowedArtists(FollowType.Artist));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Paging<SavedAlbum> GetSavedAlbums<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api)
|
||||||
|
where TSettings : SpotifySettingsBase<TSettings>, new()
|
||||||
|
{
|
||||||
|
return Execute(list, api, x => x.GetSavedAlbums(50));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Paging<PlaylistTrack> GetPlaylistTracks<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api, string id, string fields)
|
||||||
|
where TSettings : SpotifySettingsBase<TSettings>, new()
|
||||||
|
{
|
||||||
|
return Execute(list, api, x => x.GetPlaylistTracks(id, fields: fields));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Paging<T> GetNextPage<T, TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api, Paging<T> item)
|
||||||
|
where TSettings : SpotifySettingsBase<TSettings>, new()
|
||||||
|
{
|
||||||
|
return Execute(list, api, (x) => x.GetNextPage(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
public CursorPaging<T> GetNextPage<T, TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api, CursorPaging<T> item)
|
||||||
|
where TSettings : SpotifySettingsBase<TSettings>, new()
|
||||||
|
{
|
||||||
|
return Execute(list, api, (x) => x.GetNextPage(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Execute<T, TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api, Func<SpotifyWebAPI, T> method, bool allowReauth = true)
|
||||||
|
where T : BasicModel
|
||||||
|
where TSettings : SpotifySettingsBase<TSettings>, new()
|
||||||
|
{
|
||||||
|
T result = method(api);
|
||||||
|
if (result.HasError())
|
||||||
|
{
|
||||||
|
// If unauthorized, refresh token and try again
|
||||||
|
if (result.Error.Status == 401)
|
||||||
|
{
|
||||||
|
if (allowReauth)
|
||||||
|
{
|
||||||
|
_logger.Debug("Spotify authorization error, refreshing token and retrying");
|
||||||
|
list.RefreshToken();
|
||||||
|
api.AccessToken = list.AccessToken;
|
||||||
|
return Execute(list, api, method, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new SpotifyAuthorizationException(result.Error.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new SpotifyException("[{0}] {1}", result.Error.Status, result.Error.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using SpotifyAPI.Web;
|
using SpotifyAPI.Web;
|
||||||
|
using SpotifyAPI.Web.Models;
|
||||||
|
|
||||||
namespace NzbDrone.Core.ImportLists.Spotify
|
namespace NzbDrone.Core.ImportLists.Spotify
|
||||||
{
|
{
|
||||||
|
@ -17,13 +18,14 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
||||||
|
|
||||||
public class SpotifySavedAlbums : SpotifyImportListBase<SpotifySavedAlbumsSettings>
|
public class SpotifySavedAlbums : SpotifyImportListBase<SpotifySavedAlbumsSettings>
|
||||||
{
|
{
|
||||||
public SpotifySavedAlbums(IImportListStatusService importListStatusService,
|
public SpotifySavedAlbums(ISpotifyProxy spotifyProxy,
|
||||||
|
IImportListStatusService importListStatusService,
|
||||||
IImportListRepository importListRepository,
|
IImportListRepository importListRepository,
|
||||||
IConfigService configService,
|
IConfigService configService,
|
||||||
IParsingService parsingService,
|
IParsingService parsingService,
|
||||||
HttpClient httpClient,
|
IHttpClient httpClient,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
: base(importListStatusService, importListRepository, configService, parsingService, httpClient, logger)
|
: base(spotifyProxy, importListStatusService, importListRepository, configService, parsingService, httpClient, logger)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,32 +35,49 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
||||||
{
|
{
|
||||||
var result = new List<ImportListItemInfo>();
|
var result = new List<ImportListItemInfo>();
|
||||||
|
|
||||||
var albums = Execute(api, (x) => x.GetSavedAlbums(50));
|
var savedAlbums = _spotifyProxy.GetSavedAlbums(this, api);
|
||||||
_logger.Trace($"Got {albums.Total} saved albums");
|
|
||||||
|
_logger.Trace($"Got {savedAlbums?.Total ?? 0} saved albums");
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
foreach (var album in albums.Items)
|
if (savedAlbums?.Items == null)
|
||||||
{
|
{
|
||||||
var artistName = album.Album.Artists.FirstOrDefault()?.Name;
|
return result;
|
||||||
var albumName = album.Album.Name;
|
|
||||||
_logger.Trace($"Adding {artistName} - {albumName}");
|
|
||||||
|
|
||||||
if (artistName.IsNotNullOrWhiteSpace() && albumName.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
result.AddIfNotNull(new ImportListItemInfo
|
|
||||||
{
|
|
||||||
Artist = artistName,
|
|
||||||
Album = albumName
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!albums.HasNextPage())
|
|
||||||
|
foreach (var savedAlbum in savedAlbums.Items)
|
||||||
|
{
|
||||||
|
result.AddIfNotNull(ParseSavedAlbum(savedAlbum));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!savedAlbums.HasNextPage())
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
albums = Execute(api, (x) => x.GetNextPage(albums));
|
}
|
||||||
|
|
||||||
|
savedAlbums = _spotifyProxy.GetNextPage(this, api, savedAlbums);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ImportListItemInfo ParseSavedAlbum(SavedAlbum savedAlbum)
|
||||||
|
{
|
||||||
|
var artistName = savedAlbum?.Album?.Artists?.FirstOrDefault()?.Name;
|
||||||
|
var albumName = savedAlbum?.Album?.Name;
|
||||||
|
_logger.Trace($"Adding {artistName} - {albumName}");
|
||||||
|
|
||||||
|
if (artistName.IsNotNullOrWhiteSpace() && albumName.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return new ImportListItemInfo {
|
||||||
|
Artist = artistName,
|
||||||
|
Album = albumName,
|
||||||
|
ReleaseDate = ParseSpotifyDate(savedAlbum?.Album?.ReleaseDate, savedAlbum?.Album?.ReleaseDatePrecision)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue