Skip to content

Commit 1ad434b

Browse files
committed
remove reflection from spotifyuri
1 parent f57948d commit 1ad434b

File tree

4 files changed

+177
-45
lines changed

4 files changed

+177
-45
lines changed

src/commonMain/kotlin/com.adamratzman.spotify/SpotifyRestAction.kt

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,22 @@ import com.adamratzman.spotify.models.AbstractPagingObject
55
import com.adamratzman.spotify.utils.TimeUnit
66
import com.adamratzman.spotify.utils.getCurrentTimeMs
77
import com.adamratzman.spotify.utils.schedule
8+
import kotlinx.coroutines.CoroutineDispatcher
9+
import kotlinx.coroutines.Dispatchers
10+
import kotlinx.coroutines.ExperimentalCoroutinesApi
11+
import kotlinx.coroutines.FlowPreview
812
import kotlinx.coroutines.GlobalScope
13+
import kotlinx.coroutines.flow.Flow
14+
import kotlinx.coroutines.flow.asFlow
15+
import kotlinx.coroutines.flow.emitAll
16+
import kotlinx.coroutines.flow.flatMapConcat
17+
import kotlinx.coroutines.flow.flow
18+
import kotlinx.coroutines.flow.flowOn
919
import kotlinx.coroutines.launch
20+
import kotlinx.coroutines.withContext
21+
import kotlin.coroutines.resume
22+
import kotlin.coroutines.resumeWithException
23+
import kotlin.coroutines.suspendCoroutine
1024

1125
/**
1226
* Provides a uniform interface to retrieve, whether synchronously or asynchronously, [T] from Spotify
@@ -39,25 +53,49 @@ open class SpotifyRestAction<T> internal constructor(protected val api: SpotifyA
3953
}
4054
}
4155

56+
/**
57+
* Suspend the coroutine, invoke [SpotifyRestAction.supplier] asynchronously/queued and resume with result [T]
58+
* */
59+
suspend fun <T> SpotifyRestAction<T>.suspendQueue(): T {
60+
return suspendCoroutine { continuation ->
61+
queue({ throwable ->
62+
continuation.resumeWithException(throwable)
63+
}) { result ->
64+
continuation.resume(result)
65+
}
66+
}
67+
}
68+
69+
/**
70+
* Switch to given [context][dispatcher], invoke [SpotifyRestAction.supplier] and synchronously retrieve [T]
71+
*
72+
* @param dispatcher The context to execute the [SpotifyRestAction.complete] in
73+
* */
74+
@Suppress("UNCHECKED_CAST")
75+
suspend fun <T> suspendComplete(dispatcher: CoroutineDispatcher = Dispatchers.Default): T =
76+
withContext(dispatcher) {
77+
complete() as T
78+
}
79+
4280
/**
4381
* Invoke [supplier] asynchronously with no consumer
4482
*/
45-
fun queue(): SpotifyRestAction<T> = queue({}, { throw it })
83+
fun queue(): SpotifyRestAction<T> = queue({ throw it }, {})
4684

4785
/**
4886
* Invoke [supplier] asynchronously and consume [consumer] with the [T] value returned
4987
*
5088
* @param consumer to be invoked with [T] after successful completion of [supplier]
5189
*/
52-
fun queue(consumer: (T) -> Unit): SpotifyRestAction<T> = queue(consumer, {})
90+
fun queue(consumer: (T) -> Unit): SpotifyRestAction<T> = queue({ throw it }, consumer)
5391

5492
/**
5593
* Invoke [supplier] asynchronously and consume [consumer] with the [T] value returned
5694
*
5795
* @param failure Consumer to invoke when an exception is thrown by [supplier]
5896
* @param consumer to be invoked with [T] after successful completion of [supplier]
5997
*/
60-
fun queue(consumer: ((T) -> Unit), failure: ((Throwable) -> Unit)): SpotifyRestAction<T> {
98+
fun queue(failure: ((Throwable) -> Unit), consumer: ((T) -> Unit)): SpotifyRestAction<T> {
6199
hasRunBacking = true
62100
GlobalScope.launch {
63101
try {
@@ -111,3 +149,48 @@ class SpotifyRestActionPaging<Z : Any, T : AbstractPagingObject<Z>>(api: Spotify
111149
}
112150
}
113151
}
152+
153+
/**
154+
* Flow the paging action ordered. This can be less performant than [flow] if you are in the middle of the pages.
155+
* */
156+
@Suppress("UNCHECKED_CAST")
157+
@FlowPreview
158+
@ExperimentalCoroutinesApi
159+
fun <Z : Any, T : AbstractPagingObject<Z>> SpotifyRestActionPaging<Z, T>.flowOrdered(): Flow<T> = flow<T> {
160+
emitAll(flowPagingObjectsOrdered().flatMapConcat { it.asFlow() as Flow<T> })
161+
}.flowOn(Dispatchers.Default)
162+
163+
/**
164+
* Flow the paging objects ordered. This can be less performant than [flowPagingObjects] if you are in the middle of the pages.
165+
* */
166+
@ExperimentalCoroutinesApi
167+
fun <Z : Any, T : AbstractPagingObject<Z>> SpotifyRestActionPaging<Z, T>.flowPagingObjectsOrdered(): Flow<AbstractPagingObject<Z>> =
168+
flow<AbstractPagingObject<Z>> {
169+
complete().also { master ->
170+
emitAll(master.flowStartOrdered())
171+
emit(master)
172+
emitAll(master.flowEndOrdered())
173+
}
174+
}.flowOn(Dispatchers.Default)
175+
176+
/**
177+
* Flow the Paging action.
178+
* */
179+
@FlowPreview
180+
@ExperimentalCoroutinesApi
181+
fun <Z : Any, T : AbstractPagingObject<Z>> SpotifyRestActionPaging<Z, T>.flow(): Flow<Z> = flow<Z> {
182+
emitAll(flowPagingObjects().flatMapConcat { it.asFlow() })
183+
}.flowOn(Dispatchers.Default)
184+
185+
/**
186+
* Flow the paging objects.
187+
* */
188+
@ExperimentalCoroutinesApi
189+
fun <Z : Any, T : AbstractPagingObject<Z>> SpotifyRestActionPaging<Z, T>.flowPagingObjects(): Flow<AbstractPagingObject<Z>> =
190+
flow<AbstractPagingObject<Z>> {
191+
complete().also { master ->
192+
emitAll(master.flowBackward())
193+
emit(master)
194+
emitAll(master.flowForward())
195+
}
196+
}.flowOn(Dispatchers.Default)

src/commonMain/kotlin/com.adamratzman.spotify/models/PagingObjects.kt

Lines changed: 86 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,18 @@ import com.adamratzman.spotify.SpotifyApi
55
import com.adamratzman.spotify.http.SpotifyEndpoint
66
import com.adamratzman.spotify.models.serialization.toCursorBasedPagingObject
77
import com.adamratzman.spotify.models.serialization.toPagingObject
8-
import com.adamratzman.spotify.utils.catch
9-
import kotlin.reflect.KClass
8+
import kotlinx.coroutines.Dispatchers
9+
import kotlinx.coroutines.ExperimentalCoroutinesApi
10+
import kotlinx.coroutines.flow.Flow
11+
import kotlinx.coroutines.flow.asFlow
12+
import kotlinx.coroutines.flow.emitAll
13+
import kotlinx.coroutines.flow.flow
14+
import kotlinx.coroutines.flow.flowOn
15+
import kotlinx.coroutines.flow.toList
1016
import kotlinx.serialization.SerialName
1117
import kotlinx.serialization.Serializable
1218
import kotlinx.serialization.Transient
19+
import kotlin.reflect.KClass
1320

1421
/*
1522
Types used in PagingObjects and CursorBasedPagingObjects:
@@ -59,24 +66,6 @@ class PagingObject<T : Any>(
5966
override val previous: String?,
6067
override val total: Int
6168
) : AbstractPagingObject<T>(href, items, limit, next, offset, previous, total) {
62-
/**
63-
* Get the next set of [T] items
64-
*/
65-
fun getNext() = endpoint!!.toAction {
66-
catch {
67-
getImpl(PagingTraversalType.FORWARDS) as? PagingObject<T>
68-
}
69-
}
70-
71-
/**
72-
* Get the previous set of [T] items
73-
*/
74-
fun getPrevious() = endpoint!!.toAction {
75-
catch {
76-
getImpl(PagingTraversalType.BACKWARDS) as? PagingObject<T>
77-
}
78-
}
79-
8069
@Suppress("UNCHECKED_CAST")
8170
override fun getImpl(type: PagingTraversalType): AbstractPagingObject<T>? {
8271
val endpointFinal = endpoint!!
@@ -97,20 +86,20 @@ class PagingObject<T : Any>(
9786
}
9887

9988
override fun getAllImpl(): Sequence<AbstractPagingObject<T>> {
100-
val pagingObjects = mutableListOf<PagingObject<T>>()
101-
var prev = previous?.let { getPrevious().complete() }
89+
val pagingObjects = mutableListOf<AbstractPagingObject<T>>()
90+
var prev = previous?.let { getPrevious() }
10291
while (prev != null) {
10392
pagingObjects.add(prev)
104-
prev = prev.previous?.let { prev?.getPrevious()?.complete() }
93+
prev = prev.previous?.let { prev?.getPrevious() }
10594
}
10695
pagingObjects.reverse() // closer we are to current, the further we are from the start
10796

10897
pagingObjects.add(this)
10998

110-
var nxt = next?.let { getNext().complete() }
99+
var nxt = next?.let { getNext() }
111100
while (nxt != null) {
112101
pagingObjects.add(nxt)
113-
nxt = nxt.next?.let { nxt?.getNext()?.complete() }
102+
nxt = nxt.next?.let { nxt?.getNext() }
114103
}
115104
// we don't need to reverse here, as it's in order
116105
return pagingObjects.asSequence()
@@ -149,15 +138,6 @@ class CursorBasedPagingObject<T : Any>(
149138
@SerialName("cursors") val cursor: Cursor,
150139
override val total: Int
151140
) : AbstractPagingObject<T>(href, items, limit, next, 0, null, total) {
152-
/**
153-
* Get the next set of [T] items
154-
*/
155-
fun getNext() = endpoint!!.toAction {
156-
catch {
157-
getImpl(PagingTraversalType.FORWARDS) as? CursorBasedPagingObject<T>
158-
}
159-
}
160-
161141
/**
162142
* Get all CursorBasedPagingObjects associated with the request
163143
*/
@@ -226,7 +206,29 @@ abstract class AbstractPagingObject<T : Any>(
226206
@Transient open val offset: Int = 0,
227207
@Transient open val previous: String? = null,
228208
@Transient open val total: Int = -1
229-
) {
209+
) : List<T> {
210+
override val size: Int = items.size
211+
212+
override fun contains(element: T) = items.contains(element)
213+
214+
override fun containsAll(elements: Collection<T>) = items.containsAll(elements)
215+
216+
override fun get(index: Int) = items[index]
217+
218+
override fun indexOf(element: T) = items.indexOf(element)
219+
220+
override fun isEmpty() = items.isEmpty()
221+
222+
override fun iterator() = items.iterator()
223+
224+
override fun lastIndexOf(element: T) = items.lastIndexOf(element)
225+
226+
override fun listIterator() = items.listIterator()
227+
228+
override fun listIterator(index: Int) = items.listIterator(index)
229+
230+
override fun subList(fromIndex: Int, toIndex: Int) = items.subList(fromIndex, toIndex)
231+
230232
@Transient
231233
internal var endpoint: SpotifyEndpoint? = null
232234

@@ -236,13 +238,56 @@ abstract class AbstractPagingObject<T : Any>(
236238
internal abstract fun getImpl(type: PagingTraversalType): AbstractPagingObject<T>?
237239
internal abstract fun getAllImpl(): Sequence<AbstractPagingObject<T>>
238240

239-
internal fun getNextImpl() = getImpl(PagingTraversalType.FORWARDS)
240-
internal fun getPreviousImpl() = getImpl(PagingTraversalType.BACKWARDS)
241+
private fun getNextImpl() = getImpl(PagingTraversalType.FORWARDS)
242+
private fun getPreviousImpl() = getImpl(PagingTraversalType.BACKWARDS)
243+
244+
245+
fun getNext(): AbstractPagingObject<T>? = getNextImpl()
246+
fun getPrevious(): AbstractPagingObject<T>? = getPreviousImpl()
247+
248+
/**
249+
* Flow from current page backwards.
250+
* */
251+
@ExperimentalCoroutinesApi
252+
fun flowBackward(): Flow<AbstractPagingObject<T>> = flow<AbstractPagingObject<T>> {
253+
if (previous == null) return@flow
254+
var next = getPrevious()
255+
while (next != null) {
256+
emit(next)
257+
next = next.getPrevious()
258+
}
259+
}.flowOn(Dispatchers.Default)
260+
261+
/**
262+
* Flow from current page forwards.
263+
* */
264+
@ExperimentalCoroutinesApi
265+
fun flowForward(): Flow<AbstractPagingObject<T>> = flow<AbstractPagingObject<T>> {
266+
if (next == null) return@flow
267+
var next = getNext()
268+
while (next != null) {
269+
emit(next)
270+
next = next.getNext()
271+
}
272+
}.flowOn(Dispatchers.Default)
273+
274+
@ExperimentalCoroutinesApi
275+
fun flowStartOrdered(): Flow<AbstractPagingObject<T>> =
276+
flow<AbstractPagingObject<T>> {
277+
if (previous == null) return@flow
278+
flowBackward().toList().reversed().also {
279+
emitAll(it.asFlow())
280+
}
281+
}.flowOn(Dispatchers.Default)
282+
283+
@ExperimentalCoroutinesApi
284+
fun flowEndOrdered(): Flow<AbstractPagingObject<T>> = flowForward()
285+
241286
}
242287

243-
internal fun Any.instantiatePagingObjects(spotifyAPI: SpotifyApi) = when (this) {
244-
is FeaturedPlaylists -> this.playlists
288+
internal fun Any.instantiatePagingObjects(spotifyApi: SpotifyApi) = when (this) {
289+
is FeaturedPlaylists -> this.playlists
245290
is Album -> this.tracks
246-
is Playlist -> this.tracks
291+
is Playlist -> this.tracks
247292
else -> null
248-
}.let { it?.endpoint = spotifyAPI.tracks; this }
293+
}.let { it?.endpoint = spotifyApi.tracks; this }

src/commonMain/kotlin/com.adamratzman.spotify/models/SpotifyUris.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ sealed class SpotifyUri(val input: String, val type: UriType) {
7070
}
7171

7272
override fun toString(): String {
73-
return "${this::class.simpleName}($uri)"
73+
return "SpotifyUri($uri)"
7474
}
7575

7676
enum class UriType(private val typeStr: String) {

src/jvmMain/kotlin/com/adamratzman/spotify/utils/Concurrency.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
/* Spotify Web API - Kotlin Wrapper; MIT License, 2019; Original author: Adam Ratzman */
22
package com.adamratzman.spotify.utils
33

4+
import com.adamratzman.spotify.SpotifyRestAction
45
import kotlinx.coroutines.CoroutineScope
56
import kotlinx.coroutines.delay
67
import kotlinx.coroutines.launch
8+
import java.util.concurrent.CompletableFuture
79

810
actual typealias TimeUnit = java.util.concurrent.TimeUnit
911

@@ -17,3 +19,5 @@ actual fun CoroutineScope.schedule(
1719
consumer()
1820
}
1921
}
22+
23+
fun <T> SpotifyRestAction<T>.asFuture() = CompletableFuture.supplyAsync(supplier)

0 commit comments

Comments
 (0)