Skip to content

Commit 7c8f9bf

Browse files
committed
begin adding sync methods
Signed-off-by: Adam Ratzman <[email protected]>
1 parent 48003f9 commit 7c8f9bf

File tree

30 files changed

+488
-37
lines changed

30 files changed

+488
-37
lines changed

settings.gradle.kts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,6 @@ pluginManagement {
2929
}
3030

3131
rootProject.name = "spotify-web-api-kotlin"
32-
32+
include("java-interop-basic-sample")
33+
findProject(":java-interop-basic-sample")?.name = "java-interop-basic"
34+
include("java-interop-sample")

src/androidMain/kotlin/com/adamratzman/spotify/utils/PlatformUtils.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import android.app.Activity
55
import android.content.Context
66
import android.util.Log
77
import android.widget.Toast
8+
import com.soywiz.korio.async.runBlockingNoJs
9+
import kotlinx.coroutines.CoroutineScope
810
import java.net.URLEncoder
911

1012
internal actual fun String.encodeUrl() = URLEncoder.encode(this, "UTF-8")!!
@@ -33,3 +35,7 @@ internal fun toast(context: Context?, message: String?, duration: Int = Toast.LE
3335
internal fun logToConsole(message: String) {
3436
Log.i("spotify-web-api-kotlin", message)
3537
}
38+
39+
public actual fun <T> runBlockingOnJvmAndNative(block: suspend CoroutineScope.() -> T): T {
40+
return runBlockingNoJs { block() }
41+
}

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@ import com.adamratzman.spotify.endpoints.client.ClientPlaylistApi
1313
import com.adamratzman.spotify.endpoints.client.ClientProfileApi
1414
import com.adamratzman.spotify.endpoints.client.ClientSearchApi
1515
import com.adamratzman.spotify.endpoints.client.ClientShowApi
16-
import com.adamratzman.spotify.endpoints.public.AlbumApi
17-
import com.adamratzman.spotify.endpoints.public.ArtistApi
18-
import com.adamratzman.spotify.endpoints.public.BrowseApi
19-
import com.adamratzman.spotify.endpoints.public.EpisodeApi
20-
import com.adamratzman.spotify.endpoints.public.FollowingApi
21-
import com.adamratzman.spotify.endpoints.public.PlaylistApi
22-
import com.adamratzman.spotify.endpoints.public.SearchApi
23-
import com.adamratzman.spotify.endpoints.public.ShowApi
24-
import com.adamratzman.spotify.endpoints.public.TrackApi
25-
import com.adamratzman.spotify.endpoints.public.UserApi
16+
import com.adamratzman.spotify.endpoints.pub.AlbumApi
17+
import com.adamratzman.spotify.endpoints.pub.ArtistApi
18+
import com.adamratzman.spotify.endpoints.pub.BrowseApi
19+
import com.adamratzman.spotify.endpoints.pub.EpisodeApi
20+
import com.adamratzman.spotify.endpoints.pub.FollowingApi
21+
import com.adamratzman.spotify.endpoints.pub.PlaylistApi
22+
import com.adamratzman.spotify.endpoints.pub.SearchApi
23+
import com.adamratzman.spotify.endpoints.pub.ShowApi
24+
import com.adamratzman.spotify.endpoints.pub.TrackApi
25+
import com.adamratzman.spotify.endpoints.pub.UserApi
2626
import com.adamratzman.spotify.http.CacheState
2727
import com.adamratzman.spotify.http.HttpConnection
2828
import com.adamratzman.spotify.http.HttpHeader
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/* Spotify Web API, Kotlin Wrapper; MIT License, 2017-2020; Original author: Adam Ratzman */
2+
package com.adamratzman.spotify
3+
4+
import com.adamratzman.spotify.utils.TimeUnit
5+
import com.adamratzman.spotify.utils.getCurrentTimeMs
6+
import com.adamratzman.spotify.utils.runBlockingOnJvmAndNative
7+
import kotlinx.coroutines.CancellationException
8+
import kotlinx.coroutines.CoroutineScope
9+
import kotlinx.coroutines.Dispatchers
10+
import kotlinx.coroutines.GlobalScope
11+
import kotlinx.coroutines.delay
12+
import kotlinx.coroutines.launch
13+
import kotlinx.coroutines.withContext
14+
import kotlin.coroutines.CoroutineContext
15+
import kotlin.coroutines.resume
16+
import kotlin.coroutines.resumeWithException
17+
import kotlin.coroutines.suspendCoroutine
18+
import kotlin.jvm.JvmOverloads
19+
20+
/**
21+
* Provides a uniform interface to retrieve, whether synchronously or asynchronously, [T] from Spotify
22+
*/
23+
public open class SpotifyRestAction<T> internal constructor(public val supplier: suspend () -> T) {
24+
private var hasRunBacking: Boolean = false
25+
private var hasCompletedBacking: Boolean = false
26+
27+
/**
28+
* Whether this REST action has been *commenced*.
29+
*
30+
* Not to be confused with [hasCompleted]
31+
*/
32+
public fun hasRun(): Boolean = hasRunBacking
33+
34+
/**
35+
* Whether this REST action has been fully *completed*
36+
*/
37+
public fun hasCompleted(): Boolean = hasCompletedBacking
38+
39+
/**
40+
* Invoke [supplier] and synchronously retrieve [T]. This is only available on JVM/Native and will fail on JS.
41+
*/
42+
public fun complete(): T = runBlockingOnJvmAndNative {
43+
suspendComplete()
44+
}
45+
46+
/**
47+
* Suspend the coroutine, invoke [SpotifyRestAction.supplier] asynchronously/queued and resume with result [T]
48+
* */
49+
public suspend fun suspendQueue(): T = suspendCoroutine { continuation ->
50+
queue({ throwable ->
51+
continuation.resumeWithException(throwable)
52+
}) { result ->
53+
continuation.resume(result)
54+
}
55+
}
56+
57+
/**
58+
* Switch to given [context][context], invoke [SpotifyRestAction.supplier] and synchronously retrieve [T]
59+
*
60+
* @param context The context to execute the [SpotifyRestAction.complete] in
61+
* */
62+
@Suppress("UNCHECKED_CAST")
63+
@JvmOverloads
64+
public suspend fun suspendComplete(context: CoroutineContext = Dispatchers.Default): T = withContext(context) {
65+
hasRunBacking = true
66+
return@withContext try {
67+
supplier().also { hasCompletedBacking = true }
68+
} catch (e: CancellationException) {
69+
throw e
70+
} catch (e: Throwable) {
71+
throw e
72+
}
73+
}
74+
75+
/**
76+
* Invoke [supplier] asynchronously and consume [consumer] with the [T] value returned
77+
*
78+
* @param failure Consumer to invoke when an exception is thrown by [supplier]
79+
* @param consumer to be invoked with [T] after successful completion of [supplier]
80+
*/
81+
@JvmOverloads
82+
public fun queue(failure: ((Throwable) -> Unit) = { throw it }, consumer: ((T) -> Unit) = {}) {
83+
hasRunBacking = true
84+
GlobalScope.launch {
85+
try {
86+
val result = suspendComplete()
87+
consumer(result)
88+
} catch (e: CancellationException) {
89+
throw e
90+
} catch (t: Throwable) {
91+
failure(t)
92+
}
93+
}
94+
}
95+
96+
/**
97+
* Invoke [supplier] asynchronously immediately and invoke [consumer] after the specified quantity of time.
98+
*
99+
* @param quantity amount of time
100+
* @param timeUnit the unit that [quantity] is in
101+
* @param consumer to be invoked with [T] after successful completion of [supplier]
102+
*/
103+
@JvmOverloads
104+
public fun queueAfter(
105+
quantity: Int,
106+
timeUnit: TimeUnit = TimeUnit.SECONDS,
107+
scope: CoroutineScope = GlobalScope,
108+
failure: (Throwable) -> Unit = { throw it },
109+
consumer: (T) -> Unit
110+
) {
111+
val runAt = getCurrentTimeMs() + timeUnit.toMillis(quantity.toLong())
112+
GlobalScope.launch {
113+
delay(getCurrentTimeMs() - runAt)
114+
115+
try {
116+
consumer(suspendComplete())
117+
} catch (e: CancellationException) {
118+
throw e
119+
} catch (t: Throwable) {
120+
failure(t)
121+
}
122+
}
123+
}
124+
125+
override fun toString(): String = complete().toString()
126+
}

src/commonMain/kotlin/com.adamratzman.spotify/endpoints/client/ClientEpisodeApi.kt

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ package com.adamratzman.spotify.endpoints.client
33

44
import com.adamratzman.spotify.GenericSpotifyApi
55
import com.adamratzman.spotify.SpotifyException.BadRequestException
6+
import com.adamratzman.spotify.SpotifyRestAction
67
import com.adamratzman.spotify.SpotifyScope
7-
import com.adamratzman.spotify.endpoints.public.EpisodeApi
8+
import com.adamratzman.spotify.endpoints.pub.EpisodeApi
89
import com.adamratzman.spotify.models.Episode
910
import com.adamratzman.spotify.models.EpisodeList
1011
import com.adamratzman.spotify.models.EpisodeUri
@@ -39,6 +40,20 @@ public class ClientEpisodeApi(api: GenericSpotifyApi) : EpisodeApi(api) {
3940
}
4041
}
4142

43+
/**
44+
* Get Spotify catalog information for a single episode identified by its unique Spotify ID. The [Market] associated with
45+
* the user account will be used.
46+
*
47+
* **Reading the user’s resume points on episode objects requires the [SpotifyScope.USER_READ_PLAYBACK_POSITION] scope**
48+
*
49+
* **[Api Reference](https://developer.spotify.com/documentation/web-api/reference/episodes/get-an-episode/)**
50+
*
51+
* @param id The Spotify ID for the episode.
52+
*
53+
* @return possibly-null [Episode].
54+
*/
55+
public fun getEpisodeRestAction(id: String): SpotifyRestAction<Episode?> = SpotifyRestAction { getEpisode(id) }
56+
4257
/**
4358
* Get Spotify catalog information for multiple episodes based on their Spotify IDs. The [Market] associated with
4459
* the user account will be used.
@@ -64,4 +79,23 @@ public class ClientEpisodeApi(api: GenericSpotifyApi) : EpisodeApi(api) {
6479
).toObject(EpisodeList.serializer(), api, json).episodes
6580
}.flatten()
6681
}
82+
83+
/**
84+
* Get Spotify catalog information for multiple episodes based on their Spotify IDs. The [Market] associated with
85+
* the user account will be used.
86+
*
87+
* **Invalid episode ids will result in a [BadRequestException]
88+
*
89+
* **Reading the user’s resume points on episode objects requires the [SpotifyScope.USER_READ_PLAYBACK_POSITION] scope**
90+
*
91+
* **[Api Reference](https://developer.spotify.com/documentation/web-api/reference/episodes/get-several-episodes/)**
92+
*
93+
* @param ids The id or uri for the episodes. Maximum **50**.
94+
*
95+
* @return List of possibly-null [Episode] objects.
96+
* @throws BadRequestException If any invalid show id is provided
97+
*/
98+
public fun getEpisodesRestAction(vararg ids: String): SpotifyRestAction<List<Episode?>> = SpotifyRestAction {
99+
getEpisodes(*ids)
100+
}
67101
}

0 commit comments

Comments
 (0)