using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using SpotifyAPI.Web.Http; using System.Runtime.CompilerServices; namespace SpotifyAPI.Web { public class SpotifyClient : ISpotifyClient { private readonly IAPIConnector _apiConnector; public SpotifyClient(string token, string tokenType = "Bearer") : this(SpotifyClientConfig.CreateDefault(token, tokenType)) { } public SpotifyClient(SpotifyClientConfig config) { Ensure.ArgumentNotNull(config, nameof(config)); if (config.Authenticator == null) { throw new NullReferenceException("Authenticator in config is null. Please supply it via `WithAuthenticator` or `WithToken`"); } _apiConnector = new APIConnector( config.BaseAddress, config.Authenticator, config.JSONSerializer, config.HTTPClient, config.RetryHandler, config.HTTPLogger ); _apiConnector.ResponseReceived += (sender, response) => { LastResponse = response; }; DefaultPaginator = config.DefaultPaginator; UserProfile = new UserProfileClient(_apiConnector); Browse = new BrowseClient(_apiConnector); Shows = new ShowsClient(_apiConnector); Playlists = new PlaylistsClient(_apiConnector); Search = new SearchClient(_apiConnector); Follow = new FollowClient(_apiConnector); Tracks = new TracksClient(_apiConnector); Player = new PlayerClient(_apiConnector); Albums = new AlbumsClient(_apiConnector); Artists = new ArtistsClient(_apiConnector); Personalization = new PersonalizationClient(_apiConnector); Episodes = new EpisodesClient(_apiConnector); Library = new LibraryClient(_apiConnector); } public IPaginator DefaultPaginator { get; } public IUserProfileClient UserProfile { get; } public IBrowseClient Browse { get; } public IShowsClient Shows { get; } public IPlaylistsClient Playlists { get; } public ISearchClient Search { get; } public IFollowClient Follow { get; } public ITracksClient Tracks { get; } public IPlayerClient Player { get; } public IAlbumsClient Albums { get; } public IArtistsClient Artists { get; } public IPersonalizationClient Personalization { get; } public IEpisodesClient Episodes { get; } public ILibraryClient Library { get; } public IResponse? LastResponse { get; private set; } /// /// Fetches all pages and returns them grouped in a list. /// The default paginator will fetch all available resources without a delay between requests. /// This can drain your request limit quite fast, so consider using a custom paginator with delays. /// /// The first page, will be included in the output list! /// Optional. If not supplied, DefaultPaginator will be used /// The Paging-Type /// A list containing all fetched pages public Task> PaginateAll(Paging firstPage, IPaginator? paginator = null) { return (paginator ?? DefaultPaginator).PaginateAll(firstPage, _apiConnector); } /// /// Fetches all pages and returns them grouped in a list. /// The default paginator will fetch all available resources without a delay between requests. /// This can drain your request limit quite fast, so consider using a custom paginator with delays. /// /// A function to retrive the first page, will be included in the output list! /// Optional. If not supplied, DefaultPaginator will be used /// The Paging-Type /// A list containing all fetched pages public async Task> PaginateAll(Func>> getFirstPage, IPaginator? paginator = null) { Ensure.ArgumentNotNull(getFirstPage, nameof(getFirstPage)); var firstPage = await getFirstPage().ConfigureAwait(false); return await (paginator ?? DefaultPaginator).PaginateAll(firstPage, _apiConnector).ConfigureAwait(false); } /// /// Fetches all pages and returns them grouped in a list. /// The default paginator will fetch all available resources without a delay between requests. /// This can drain your request limit quite fast, so consider using a custom paginator with delays. /// /// A task to retrive the first page, will be included in the output list! /// Optional. If not supplied, DefaultPaginator will be used /// The Paging-Type /// A list containing all fetched pages public async Task> PaginateAll(Task> firstPageTask, IPaginator? paginator = null) { Ensure.ArgumentNotNull(firstPageTask, nameof(firstPageTask)); var firstPage = await firstPageTask.ConfigureAwait(false); return await (paginator ?? DefaultPaginator).PaginateAll(firstPage, _apiConnector).ConfigureAwait(false); } /// /// Fetches all pages and returns them grouped in a list. /// Some responses (e.g search response) have the pagination nested in a JSON Property. /// To workaround this limitation, the mapper is required and needs to point to the correct next pagination. /// The default paginator will fetch all available resources without a delay between requests. /// This can drain your request limit quite fast, so consider using a custom paginator with delays. /// /// A first page, will be included in the output list! /// A function which maps response objects to the next paging object /// Optional. If not supplied, DefaultPaginator will be used /// The Paging-Type /// The Response-Type /// A list containing all fetched pages public Task> PaginateAll( Paging firstPage, Func> mapper, IPaginator? paginator = null ) { return (paginator ?? DefaultPaginator).PaginateAll(firstPage, mapper, _apiConnector); } /// /// Fetches all pages and returns them grouped in a list. /// Some responses (e.g search response) have the pagination nested in a JSON Property. /// To workaround this limitation, the mapper is required and needs to point to the correct next pagination. /// The default paginator will fetch all available resources without a delay between requests. /// This can drain your request limit quite fast, so consider using a custom paginator with delays. /// /// A function to retrive the first page, will be included in the output list! /// A function which maps response objects to the next paging object /// Optional. If not supplied, DefaultPaginator will be used /// The Paging-Type /// The Response-Type /// public async Task> PaginateAll( Func>> getFirstPage, Func> mapper, IPaginator? paginator = null ) { Ensure.ArgumentNotNull(getFirstPage, nameof(getFirstPage)); var firstPage = await getFirstPage().ConfigureAwait(false); return await (paginator ?? DefaultPaginator).PaginateAll(firstPage, mapper, _apiConnector).ConfigureAwait(false); } /// /// Fetches all pages and returns them grouped in a list. /// Some responses (e.g search response) have the pagination nested in a JSON Property. /// To workaround this limitation, the mapper is required and needs to point to the correct next pagination. /// The default paginator will fetch all available resources without a delay between requests. /// This can drain your request limit quite fast, so consider using a custom paginator with delays. /// /// A Task to retrive the first page, will be included in the output list! /// A function which maps response objects to the next paging object /// Optional. If not supplied, DefaultPaginator will be used /// The Paging-Type /// The Response-Type /// public async Task> PaginateAll( Task> firstPageTask, Func> mapper, IPaginator? paginator = null ) { Ensure.ArgumentNotNull(firstPageTask, nameof(firstPageTask)); var firstPage = await firstPageTask.ConfigureAwait(false); return await (paginator ?? DefaultPaginator).PaginateAll(firstPage, mapper, _apiConnector).ConfigureAwait(false); } #if NETSTANDARD2_1 /// /// Paginate through pages by using IAsyncEnumerable, introduced in C# 8 /// The default paginator will fetch all available resources without a delay between requests. /// This can drain your request limit quite fast, so consider using a custom paginator with delays. /// /// A first page, will be included in the output list! /// Optional. If not supplied, DefaultPaginator will be used /// An optional Cancellation Token /// The Paging-Type /// An iterable IAsyncEnumerable public IAsyncEnumerable Paginate( Paging firstPage, IPaginator? paginator = null, CancellationToken cancellationToken = default ) { return (paginator ?? DefaultPaginator).Paginate(firstPage, _apiConnector, cancellationToken); } /// /// Paginate through pages by using IAsyncEnumerable, introduced in C# 8 /// The default paginator will fetch all available resources without a delay between requests. /// This can drain your request limit quite fast, so consider using a custom paginator with delays. /// /// A Function to retrive the first page, will be included in the output list! /// Optional. If not supplied, DefaultPaginator will be used /// An optional Cancellation Token /// The Paging-Type /// An iterable IAsyncEnumerable public async IAsyncEnumerable Paginate( Func>> getFirstPage, IPaginator? paginator = null, [EnumeratorCancellation] CancellationToken cancellationToken = default ) { Ensure.ArgumentNotNull(getFirstPage, nameof(getFirstPage)); var firstPage = await getFirstPage().ConfigureAwait(false); await foreach (var item in (paginator ?? DefaultPaginator) .Paginate(firstPage, _apiConnector) .WithCancellation(cancellationToken) ) { yield return item; } } /// /// Paginate through pages by using IAsyncEnumerable, introduced in C# 8 /// The default paginator will fetch all available resources without a delay between requests. /// This can drain your request limit quite fast, so consider using a custom paginator with delays. /// /// A Task to retrive the first page, will be included in the output list! /// Optional. If not supplied, DefaultPaginator will be used /// An optional Cancellation Token /// The Paging-Type /// An iterable IAsyncEnumerable public async IAsyncEnumerable Paginate( Task> firstPageTask, IPaginator? paginator = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(firstPageTask, nameof(firstPageTask)); var firstPage = await firstPageTask.ConfigureAwait(false); await foreach (var item in (paginator ?? DefaultPaginator) .Paginate(firstPage, _apiConnector) .WithCancellation(cancellationToken) ) { yield return item; } } /// /// Paginate through pages by using IAsyncEnumerable, introduced in C# 8 /// Some responses (e.g search response) have the pagination nested in a JSON Property. /// To workaround this limitation, the mapper is required and needs to point to the correct next pagination. /// The default paginator will fetch all available resources without a delay between requests. /// This can drain your request limit quite fast, so consider using a custom paginator with delays. /// /// A first page, will be included in the output list! /// A function which maps response objects to the next paging object /// Optional. If not supplied, DefaultPaginator will be used /// An optional Cancellation Token /// The Paging-Type /// The Response-Type /// public IAsyncEnumerable Paginate( Paging firstPage, Func> mapper, IPaginator? paginator = null, CancellationToken cancellationToken = default ) { return (paginator ?? DefaultPaginator).Paginate(firstPage, mapper, _apiConnector, cancellationToken); } /// /// Paginate through pages by using IAsyncEnumerable, introduced in C# 8 /// Some responses (e.g search response) have the pagination nested in a JSON Property. /// To workaround this limitation, the mapper is required and needs to point to the correct next pagination. /// The default paginator will fetch all available resources without a delay between requests. /// This can drain your request limit quite fast, so consider using a custom paginator with delays. /// /// A Function to retrive the first page, will be included in the output list! /// A function which maps response objects to the next paging object /// Optional. If not supplied, DefaultPaginator will be used /// An optional Cancellation Token /// The Paging-Type /// The Response-Type /// public async IAsyncEnumerable Paginate( Func>> getFirstPage, Func> mapper, IPaginator? paginator = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(getFirstPage, nameof(getFirstPage)); var firstPage = await getFirstPage().ConfigureAwait(false); await foreach (var item in (paginator ?? DefaultPaginator) .Paginate(firstPage, mapper, _apiConnector) .WithCancellation(cancellationToken) ) { yield return item; } } /// /// Paginate through pages by using IAsyncEnumerable, introduced in C# 8 /// Some responses (e.g search response) have the pagination nested in a JSON Property. /// To workaround this limitation, the mapper is required and needs to point to the correct next pagination. /// The default paginator will fetch all available resources without a delay between requests. /// This can drain your request limit quite fast, so consider using a custom paginator with delays. /// /// A Task to retrive the first page, will be included in the output list! /// A function which maps response objects to the next paging object /// Optional. If not supplied, DefaultPaginator will be used /// An optional Cancellation Token /// The Paging-Type /// The Response-Type /// public async IAsyncEnumerable Paginate( Task> firstPageTask, Func> mapper, IPaginator? paginator = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(firstPageTask, nameof(firstPageTask)); var firstPage = await firstPageTask.ConfigureAwait(false); await foreach (var item in (paginator ?? DefaultPaginator) .Paginate(firstPage, mapper, _apiConnector) .WithCancellation(cancellationToken) ) { yield return item; } } #endif } }