Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/* Spotify Web API, Kotlin Wrapper; MIT License, 2017-2022; Original author: Adam Ratzman */
package com.adamratzman.spotify.models
import com.adamratzman.spotify.GenericSpotifyApi
import com.adamratzman.spotify.SpotifyRestAction
import com.adamratzman.spotify.models.PagingTraversalType.BACKWARDS
import com.adamratzman.spotify.models.PagingTraversalType.FORWARDS
import com.adamratzman.spotify.models.serialization.instantiateAllNeedsApiObjects
import com.adamratzman.spotify.models.serialization.instantiateLateinitsForPagingObject
import com.adamratzman.spotify.models.serialization.toCursorBasedPagingObject
import com.adamratzman.spotify.models.serialization.toNonNullablePagingObject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.toList
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlin.coroutines.CoroutineContext
import kotlin.reflect.KClass
/*
Types used in PagingObjects and CursorBasedPagingObjects:
CursorBasedPagingObject:
PlayHistory
Artist
PagingObject:
SimpleTrack
SimpleAlbum
SpotifyCategory
SimplePlaylist
SavedTrack
SavedAlbum
Artist
Track
PlaylistTrack
*/
public enum class PagingTraversalType {
BACKWARDS,
FORWARDS;
}
/**
* The offset-based nullable paging object is a container for a set of objects. It contains a key called items
* (whose value is an array of the requested objects) along with other keys like previous, next and
* limit that can be useful in future calls. Its items are not guaranteed to be not null
*/
@Serializable
public class NullablePagingObject(
override val href: String,
override val items: List,
override val limit: Int,
override val next: String? = null,
override val offset: Int,
override val previous: String? = null,
override val total: Int = 0
) : AbstractPagingObject>() {
public fun toPagingObject(): PagingObject {
val pagingObject = PagingObject(
href,
items.filterNotNull(),
limit,
next,
offset,
previous,
total
)
pagingObject.instantiateLateinitsForPagingObject(itemClass, api)
return pagingObject
}
override fun iterator(): Iterator = items.iterator()
override fun listIterator(): ListIterator = items.listIterator()
override fun listIterator(index: Int): ListIterator = items.listIterator(index)
override fun subList(fromIndex: Int, toIndex: Int): List = items.subList(fromIndex, toIndex)
}
/**
* The offset-based non-nullable paging object is a container for a set of objects. It contains a key called items
* (whose value is an array of the requested objects) along with other keys like previous, next and
* limit that can be useful in future calls.
*/
@Serializable
public data class PagingObject(
override val href: String,
override val items: List,
override val limit: Int,
override val next: String? = null,
override val offset: Int,
override val previous: String? = null,
override val total: Int = 0
) : AbstractPagingObject>() {
override fun get(index: Int): T = super.get(index)!!
override fun iterator(): Iterator = items.iterator()
override fun listIterator(): ListIterator = items.listIterator()
override fun listIterator(index: Int): ListIterator = items.listIterator(index)
override fun subList(fromIndex: Int, toIndex: Int): List = items.subList(fromIndex, toIndex)
override suspend fun take(n: Int): List {
return super.take(n).filterNotNull()
}
}
/**
* The offset-based paging object is a container for a set of objects. It contains a key called items
* (whose value is an array of the requested objects) along with other keys like previous, next and
* limit that can be useful in future calls.
*
* @property href A link to the Web API endpoint returning the full result of the request.
* @property items The requested data.
* @property limit The maximum number of items in the response (as set in the query or by default).
* @property next URL to the next page of items. ( null if none)
* @property previous URL to the previous page of items. ( null if none)
* @property total The maximum number of items available to return.
* @property offset The offset of the items returned (as set in the query or by default).
*/
@Serializable
public abstract class AbstractPagingObject> :
PagingObjectBase(),
List {
@Suppress("UNCHECKED_CAST")
override suspend fun get(type: PagingTraversalType): Z? {
return (if (type == FORWARDS) next else previous)?.let { api.defaultEndpoint.get(it) }?.let { json ->
when (itemClass) {
SimpleTrack::class -> json.toNonNullablePagingObject(
SimpleTrack.serializer(),
null,
api,
api.spotifyApiOptions.json,
true
)
SpotifyCategory::class -> json.toNonNullablePagingObject(
SpotifyCategory.serializer(),
null,
api,
api.spotifyApiOptions.json,
true
)
SimpleAlbum::class -> json.toNonNullablePagingObject(
SimpleAlbum.serializer(),
null,
api,
api.spotifyApiOptions.json,
true
)
SimplePlaylist::class -> json.toNonNullablePagingObject(
SimplePlaylist.serializer(),
null,
api,
api.spotifyApiOptions.json,
true
)
SavedTrack::class -> json.toNonNullablePagingObject(
SavedTrack.serializer(),
null,
api,
api.spotifyApiOptions.json,
true
)
SavedAlbum::class -> json.toNonNullablePagingObject(
SavedAlbum.serializer(),
null,
api,
api.spotifyApiOptions.json,
true
)
Artist::class -> json.toNonNullablePagingObject(
Artist.serializer(),
null,
api,
api.spotifyApiOptions.json,
true
)
Track::class -> json.toNonNullablePagingObject(
Track.serializer(),
null,
api,
api.spotifyApiOptions.json,
true
)
PlaylistTrack::class -> json.toNonNullablePagingObject(
PlaylistTrack.serializer(),
null,
api,
api.spotifyApiOptions.json,
true
)
else -> throw IllegalArgumentException("Unknown type ($itemClass) in $href response")
} as? Z
}
}
override suspend fun getWithNextTotalPagingObjects(total: Int): List {
@Suppress("UNCHECKED_CAST")
val pagingObjects = mutableListOf(this as Z)
var nxt = next?.let { getNext() }
while (pagingObjects.size < total && nxt != null) {
pagingObjects.add(nxt)
nxt = nxt.next?.let { nxt?.getNext() }
}
return pagingObjects.distinctBy { it.href }
}
override suspend fun getAllPagingObjects(): List {
val pagingObjects = mutableListOf()
var prev = previous?.let { getPrevious() }
while (prev != null) {
pagingObjects.add(prev)
prev = prev.previous?.let { prev?.getPrevious() }
}
pagingObjects.reverse() // closer we are to current, the further we are from the start
@Suppress("UNCHECKED_CAST")
pagingObjects.add(this as Z)
var nxt = next?.let { getNext() }
while (nxt != null) {
pagingObjects.add(nxt)
nxt = nxt.next?.let { nxt?.getNext() }
}
// we don't need to reverse here, as it's in order
return pagingObjects
}
/**
* Synchronously retrieve the next [total] paging objects associated with this [AbstractPagingObject], including this [AbstractPagingObject].
*
* @param total The total amount of [AbstractPagingObject] to request, which includes this [AbstractPagingObject].
* @since 3.0.0
*/
@Suppress("UNCHECKED_CAST")
public suspend fun getWithNext(total: Int): List = getWithNextTotalPagingObjects(total)
/**
* Get all items of type [T] associated with the request
*/
public override suspend fun getAllItems(): List = getAllPagingObjects().map { it.items }.flatten()
}
/**
* The cursor-based paging object is a container for a set of objects. It contains a key called
* items (whose value is an array of the requested objects) along with other keys like next and
* cursors that can be useful in future calls.
*
* @param href A link to the Web API endpoint returning the full result of the request.
* @param items The requested data.
* @param limit The maximum number of items in the response (as set in the query or by default).
* @param next URL to the next page of items. ( null if none)
* @param total The maximum number of items available to return.
* @param cursor The cursors used to find the next set of items. If [items] is empty, cursor may be null.
*/
@Serializable
public data class CursorBasedPagingObject(
override val href: String,
override val items: List,
override val limit: Int,
override val next: String? = null,
@SerialName("cursors") public val cursor: Cursor? = null,
override val total: Int = 0,
override val offset: Int = 0,
override val previous: String? = null
) : PagingObjectBase>() {
/**
* Synchronously retrieve the next [total] paging objects associated with this [CursorBasedPagingObject], including this [CursorBasedPagingObject].
*
* @param total The total amount of [CursorBasedPagingObject] to request, which includes this [CursorBasedPagingObject].
* @since 3.0.0
*/
@Suppress("UNCHECKED_CAST")
public suspend fun getWithNext(total: Int): List> = getWithNextTotalPagingObjects(total)
/**
* Get all items of type [T] associated with the request
*/
override suspend fun getAllItems(): List = getAllPagingObjects().map { it.items }.flatten()
override suspend fun get(type: PagingTraversalType): CursorBasedPagingObject? {
require(type != BACKWARDS) { "CursorBasedPagingObjects only can go forwards" }
return next?.let { getCursorBasedPagingObject(it) }
}
@Suppress("UNCHECKED_CAST")
public suspend fun getCursorBasedPagingObject(url: String): CursorBasedPagingObject? {
val json = api.defaultEndpoint.get(url)
return when (itemClass) {
PlayHistory::class -> json.toCursorBasedPagingObject(
PlayHistory::class,
PlayHistory.serializer(),
null,
api,
api.spotifyApiOptions.json
)
Artist::class -> json.toCursorBasedPagingObject(
Artist::class,
Artist.serializer(),
null,
api,
api.spotifyApiOptions.json
)
else -> throw IllegalArgumentException("Unknown type in $href")
} as? CursorBasedPagingObject
}
override suspend fun getAllPagingObjects(): List> {
val pagingObjects = mutableListOf>()
var currentPagingObject = this@CursorBasedPagingObject
pagingObjects.add(currentPagingObject)
while (true) {
currentPagingObject = currentPagingObject.get(FORWARDS) ?: break
pagingObjects.add(currentPagingObject)
}
return pagingObjects
}
override suspend fun getWithNextTotalPagingObjects(total: Int): List> {
val pagingObjects = mutableListOf(this)
var nxt = getNext()
while (pagingObjects.size < total && nxt != null) {
pagingObjects.add(nxt)
nxt = nxt.next?.let { nxt?.getNext() }
}
return pagingObjects.distinctBy { it.href }
}
override fun get(index: Int): T = super.get(index)!!
override fun iterator(): Iterator = items.iterator()
override fun listIterator(): ListIterator = items.listIterator()
override fun listIterator(index: Int): ListIterator = items.listIterator(index)
override fun subList(fromIndex: Int, toIndex: Int): List = items.subList(fromIndex, toIndex)
override suspend fun take(n: Int): List {
return super.take(n).filterNotNull()
}
}
/**
* The cursor to use as key to find the next (or previous) page of items.
*
* @param before The cursor to use as key to find the previous page of items.
* @param after The cursor to use as key to find the next page of items.
*/
@Serializable
public data class Cursor(val before: String? = null, val after: String? = null)
/**
* @property href A link to the Web API endpoint returning the full result of the request.
* @property items The requested data.
* @property limit The maximum number of items in the response (as set in the query or by default).
* @property next URL to the next page of items. ( null if none)
* @property previous URL to the previous page of items. ( null if none)
* @property total The maximum number of items available to return.
* @property offset The offset of the items returned (as set in the query or by default).
*/
@Serializable
public abstract class PagingObjectBase> : List, NeedsApi() {
public abstract val href: String
public abstract val items: List
public abstract val limit: Int
public abstract val next: String?
public abstract val offset: Int
public abstract val previous: String?
public abstract val total: Int
@Suppress("UNCHECKED_CAST")
override fun getMembersThatNeedApiInstantiation(): List {
return if (items.getOrNull(0) !is NeedsApi) {
listOf(this)
} else {
(items as List) + listOf(this)
}
}
@Transient
internal var itemClass: KClass? = null
internal abstract suspend fun get(type: PagingTraversalType): Z?
/**
* Retrieve all [PagingObjectBase] associated with this rest action
*/
public abstract suspend fun getAllPagingObjects(): List
/**
* Retrieve all [PagingObjectBase] associated with this rest action
*/
public fun getAllPagingObjectsRestAction(): SpotifyRestAction> = SpotifyRestAction { getAllPagingObjects() }
/**
* Retrieve all [T] associated with this rest action
*/
public abstract suspend fun getAllItems(): List
/**
* Retrieve all [T] associated with this rest action
*/
public fun getAllItemsRestAction(): SpotifyRestAction> = SpotifyRestAction { getAllItems() }
/**
* Synchronously retrieve the next [total] paging objects associated with this [PagingObjectBase], including this [PagingObjectBase].
*
* @param total The total amount of [PagingObjectBase] to request, which includes this [PagingObjectBase].
* @since 3.0.0
*/
public abstract suspend fun getWithNextTotalPagingObjects(total: Int): List
/**
* Synchronously retrieve the next [total] paging objects associated with this [PagingObjectBase], including this [PagingObjectBase].
*
* @param total The total amount of [PagingObjectBase] to request, which includes this [PagingObjectBase].
* @since 3.0.0
*/
public fun getWithNextTotalPagingObjectsRestAction(total: Int): SpotifyRestAction> =
SpotifyRestAction { getWithNextTotalPagingObjects(total) }
public suspend fun getNext(): Z? = get(FORWARDS)
public fun getNextRestAction(): SpotifyRestAction = SpotifyRestAction { getNext() }
public suspend fun getPrevious(): Z? = get(BACKWARDS)
public fun getPreviousRestAction(): SpotifyRestAction = SpotifyRestAction { getPrevious() }
/**
* Get all items of type [T] associated with the request. Filters out null objects.
*/
public suspend fun getAllItemsNotNull(): List = getAllItems().filterNotNull()
/**
* Get all items of type [T] associated with the request. Filters out null objects.
*/
public fun getAllItemsNotNullRestAction(): SpotifyRestAction> = SpotifyRestAction { getAllItemsNotNull() }
/**
* Retrieve the items associated with the next [total] paging objects associated with this rest action, including the current one.
*
* @param total The total amount of [PagingObjectBase] to request, including the [PagingObjectBase] associated with the current request.
* @since 3.0.0
*/
public suspend fun getWithNextItems(total: Int): List =
getWithNextTotalPagingObjects(total).map { it.items }.flatten()
/**
* Retrieve the items associated with the next [total] paging objects associated with this rest action, including the current one.
*
* @param total The total amount of [PagingObjectBase] to request, including the [PagingObjectBase] associated with the current request.
* @since 3.0.0
*/
public fun getWithNextItemsRestAction(total: Int): SpotifyRestAction> =
SpotifyRestAction { getWithNextItems(total) }
/**
* Flow from current page backwards.
* */
public fun flowBackward(): Flow = flow {
if (previous == null) return@flow
var next = getPrevious()
while (next != null) {
emit(next)
next = next.getPrevious()
}
}.flowOn(Dispatchers.Default)
/**
* Flow from current page forwards.
* */
@ExperimentalCoroutinesApi
public fun flowForward(): Flow = flow {
if (next == null) return@flow
var next = getNext()
while (next != null) {
emit(next)
next = next.getNext()
}
}.flowOn(Dispatchers.Default)
@ExperimentalCoroutinesApi
public fun flowStartOrdered(): Flow =
flow {
if (previous == null) return@flow
flowBackward().toList().reversed().also {
emitAll(it.asFlow())
}
}.flowOn(Dispatchers.Default)
@ExperimentalCoroutinesApi
public fun flowEndOrdered(): Flow = flowForward()
/**
* Flow the paging action ordered. This can be less performant than [flow] if you are in the middle of the pages.
* */
@FlowPreview
@ExperimentalCoroutinesApi
public fun flowOrdered(context: CoroutineContext = Dispatchers.Default): Flow = flow {
emitAll(flowPagingObjectsOrdered().flatMapConcat { it.asFlow() })
}.flowOn(context)
/**
* Flow the paging objects ordered. This can be less performant than [flowPagingObjects] if you are in the middle of the pages.
* */
@ExperimentalCoroutinesApi
public fun flowPagingObjectsOrdered(context: CoroutineContext = Dispatchers.Default): Flow =
flow {
[email protected] { master ->
emitAll(master.flowStartOrdered())
@Suppress("UNCHECKED_CAST")
emit(master as Z)
emitAll(master.flowEndOrdered())
}
}.flowOn(context)
/**
* Flow the Paging action.
* */
@FlowPreview
@ExperimentalCoroutinesApi
public fun flow(context: CoroutineContext = Dispatchers.Default): Flow = flow {
emitAll(flowPagingObjects().flatMapConcat { it.asFlow() })
}.flowOn(context)
/**
* Flow the paging objects.
* */
@ExperimentalCoroutinesApi
public fun flowPagingObjects(context: CoroutineContext = Dispatchers.Default): Flow =
flow {
[email protected] { master ->
emitAll(master.flowBackward())
@Suppress("UNCHECKED_CAST")
emit(master as Z)
emitAll(master.flowForward())
}
}.flowOn(context)
override val size: Int get() = items.size
override fun contains(element: T?): Boolean = items.contains(element)
override fun containsAll(elements: Collection): Boolean = items.containsAll(elements)
override fun indexOf(element: T?): Int = items.indexOf(element)
override fun isEmpty(): Boolean = items.isEmpty()
override fun lastIndexOf(element: T?): Int = items.lastIndexOf(element)
override fun get(index: Int): T? = items[index]
/**
* Returns a list containing at most first [n] elements. Note that additional requests may be performed.
* The [limit] used in the request used to produce this [PagingObjectBase] will be respected, so choose [limit] carefully.
*/
public open suspend fun take(n: Int): List {
if (n < 0) throw IllegalArgumentException("n must be non-negative.")
if (n in items.indices) return items.take(n)
return items + (getNext()?.take(n - size) ?: listOf())
}
}
internal fun Any.instantiateLateinitsIfPagingObjects(api: GenericSpotifyApi) = when (this) {
is FeaturedPlaylists -> {
this.playlists.itemClass = SimplePlaylist::class
listOf(this.playlists)
}
is Show -> {
this.episodes.itemClass = SimpleEpisode::class
listOf(this.episodes)
}
is Album -> {
this.tracks.itemClass = SimpleTrack::class
listOf(this.tracks)
}
is Playlist -> {
this.tracks.itemClass = PlaylistTrack::class
listOf(this.tracks)
}
is SpotifySearchResult -> {
this.albums?.itemClass = SimpleAlbum::class
this.artists?.itemClass = Artist::class
this.episodes?.itemClass = SimpleEpisode::class
this.playlists?.itemClass = SimplePlaylist::class
this.shows?.itemClass = SimpleShow::class
this.tracks?.itemClass = Track::class
listOfNotNull(albums, artists, episodes, playlists, shows, tracks)
}
else -> null
}?.let { objs ->
objs.forEach { obj ->
obj.api = api
obj.getMembersThatNeedApiInstantiation().instantiateAllNeedsApiObjects(api)
}
}