switch to okio official coroutines

This commit is contained in:
Chloe committed 2026-02-01 01:28:07 -07:00
1 parent d0715c1bd8
commit 1f1e0de523
3 files changed
+131 -115

No files matched your search

+3
View File
@@ -100,8 +100,11 @@ dependencies {
implementation ("androidx.activity:activity-ktx:1.12.3")
implementation ("androidx.appcompat:appcompat:1.7.1")
implementation ("androidx.documentfile:documentfile:1.1.0")
implementation ("com.squareup.okio:okio:3.16.4")
implementation ("com.squareup.okhttp3:okhttp:5.3.2")
implementation ("com.squareup.okhttp3:okhttp-coroutines:5.3.2")
implementation ("org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0")
implementation ("org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.10.0")
implementation ("org.jetbrains.kotlinx:kotlinx-datetime:0.7.1")
@@ -1,6 +1,5 @@
package com.geode.launcher.updater
import com.geode.launcher.utils.DownloadUtils.executeCoroutine
import kotlin.time.Clock
import kotlinx.datetime.DateTimeUnit
import kotlin.time.Instant
@@ -12,6 +11,7 @@ import kotlinx.serialization.json.okio.decodeFromBufferedSource
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.coroutines.executeAsync
import java.io.IOException
import java.net.URL
import kotlin.time.ExperimentalTime
@@ -62,45 +62,46 @@ class ReleaseRepository(private val httpClient: OkHttpClient) {
.build()
val call = httpClient.newCall(request)
val response = call.executeCoroutine()
return when (response.code) {
200 -> {
val format = Json {
namingStrategy = JsonNamingStrategy.SnakeCase
ignoreUnknownKeys = true
return call.executeAsync().use { response ->
when (response.code) {
200 -> {
val format = Json {
namingStrategy = JsonNamingStrategy.SnakeCase
ignoreUnknownKeys = true
}
val release = format.decodeFromBufferedSource<List<Release>>(
response.body.source()
)
(release.find { it.tagName != "nightly" })
?.let(::DownloadableGitHubLoaderRelease)
}
403 -> {
// determine if the error code is a ratelimit
// (github docs say it sends 429 too, but haven't seen that)
val release = format.decodeFromBufferedSource<List<Release>>(
response.body!!.source()
)
val limitRemaining = response.headers.get(GITHUB_RATELIMIT_REMAINING)?.toInt()
val limitReset = response.headers.get(GITHUB_RATELIMIT_RESET)?.toLong()
(release.find { it.tagName != "nightly" })
?.let(::DownloadableGitHubLoaderRelease)
}
403 -> {
// determine if the error code is a ratelimit
// (github docs say it sends 429 too, but haven't seen that)
if (limitRemaining == 0 && limitReset != null) {
// handle ratelimit with a custom error
// there's also a retry-after header but again, haven't seen
val resetTime = Instant.fromEpochSeconds(limitReset, 0L)
val msg = generateRateLimitMessage(resetTime)
val limitRemaining = response.headers.get(GITHUB_RATELIMIT_REMAINING)?.toInt()
val limitReset = response.headers.get(GITHUB_RATELIMIT_RESET)?.toLong()
throw IOException(msg)
}
if (limitRemaining == 0 && limitReset != null) {
// handle ratelimit with a custom error
// there's also a retry-after header but again, haven't seen
val resetTime = Instant.fromEpochSeconds(limitReset, 0L)
val msg = generateRateLimitMessage(resetTime)
throw IOException(msg)
throw IOException("response 403: ${response.body.string()}")
}
404 -> {
null
}
else -> {
throw IOException("unknown response ${response.code}")
}
throw IOException("response 403: ${response.body!!.string()}")
}
404 -> {
null
}
else -> {
throw IOException("unknown response ${response.code}")
}
}
}
@@ -124,44 +125,44 @@ class ReleaseRepository(private val httpClient: OkHttpClient) {
.build()
val call = httpClient.newCall(request)
val response = call.executeCoroutine()
return call.executeAsync().use { response ->
when (response.code) {
200 -> {
val format = Json {
namingStrategy = JsonNamingStrategy.SnakeCase
ignoreUnknownKeys = true
}
return when (response.code) {
200 -> {
val format = Json {
namingStrategy = JsonNamingStrategy.SnakeCase
ignoreUnknownKeys = true
val release = format.decodeFromBufferedSource<Release>(
response.body.source()
)
release
}
403 -> {
// determine if the error code is a ratelimit
// (github docs say it sends 429 too, but haven't seen that)
val release = format.decodeFromBufferedSource<Release>(
response.body!!.source()
)
val limitRemaining = response.headers[GITHUB_RATELIMIT_REMAINING]?.toInt()
val limitReset = response.headers[GITHUB_RATELIMIT_RESET]?.toLong()
release
}
403 -> {
// determine if the error code is a ratelimit
// (github docs say it sends 429 too, but haven't seen that)
if (limitRemaining == 0 && limitReset != null) {
// handle ratelimit with a custom error
// there's also a retry-after header but again, haven't seen
val resetTime = Instant.fromEpochSeconds(limitReset, 0L)
val msg = generateRateLimitMessage(resetTime)
val limitRemaining = response.headers[GITHUB_RATELIMIT_REMAINING]?.toInt()
val limitReset = response.headers[GITHUB_RATELIMIT_RESET]?.toLong()
throw IOException(msg)
}
if (limitRemaining == 0 && limitReset != null) {
// handle ratelimit with a custom error
// there's also a retry-after header but again, haven't seen
val resetTime = Instant.fromEpochSeconds(limitReset, 0L)
val msg = generateRateLimitMessage(resetTime)
throw IOException(msg)
throw IOException("response 403: ${response.body.string()}")
}
404 -> {
null
}
else -> {
throw IOException("unknown response ${response.code}")
}
throw IOException("response 403: ${response.body!!.string()}")
}
404 -> {
null
}
else -> {
throw IOException("unknown response ${response.code}")
}
}
}
@@ -181,28 +182,28 @@ class ReleaseRepository(private val httpClient: OkHttpClient) {
.build()
val call = httpClient.newCall(request)
val response = call.executeCoroutine()
return call.executeAsync().use { response ->
when (response.code) {
200 -> {
val format = Json {
namingStrategy = JsonNamingStrategy.SnakeCase
ignoreUnknownKeys = true
}
return when (response.code) {
200 -> {
val format = Json {
namingStrategy = JsonNamingStrategy.SnakeCase
ignoreUnknownKeys = true
val body = format.decodeFromBufferedSource<LoaderPayload<LoaderVersion>>(
response.body.source()
)
val release = body.payload ?: throw IOException("index error: ${body.error}")
DownloadableLoaderRelease(release)
}
404 -> {
null
}
else -> {
throw IOException("unknown response ${response.code}")
}
val body = format.decodeFromBufferedSource<LoaderPayload<LoaderVersion>>(
response.body!!.source()
)
val release = body.payload ?: throw IOException("index error: ${body.error}")
DownloadableLoaderRelease(release)
}
404 -> {
null
}
else -> {
throw IOException("unknown response ${response.code}")
}
}
}
@@ -3,17 +3,18 @@ package com.geode.launcher.utils
import android.os.Build
import android.os.FileUtils
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.Call
import okhttp3.Callback
import okhttp3.MediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.RequestBody
import okhttp3.ResponseBody
import okhttp3.coroutines.executeAsync
import okio.Buffer
import okio.BufferedSink
import okio.BufferedSource
import okio.ForwardingSink
import okio.ForwardingSource
import okio.Sink
import okio.Source
import okio.buffer
import java.io.IOException
@@ -21,8 +22,6 @@ import java.io.InputStream
import java.io.OutputStream
import java.util.concurrent.TimeUnit
import java.util.zip.ZipInputStream
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
typealias ProgressCallback = (progress: Long, outOf: Long) -> Unit
@@ -50,7 +49,7 @@ object DownloadUtils {
val originalResponse = chain.proceed(chain.request())
originalResponse.newBuilder()
.body(ProgressResponseBody(
originalResponse.body!!, onProgress
originalResponse.body, onProgress
))
.build()
}
@@ -59,30 +58,8 @@ object DownloadUtils {
val progressClient = progressClientBuilder.build()
val call = progressClient.newCall(request)
val response = call.executeCoroutine()
return response.body!!.byteStream()
}
suspend fun Call.executeCoroutine(): Response {
return suspendCancellableCoroutine { continuation ->
this.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
if (continuation.isCancelled) {
return
}
continuation.resumeWithException(e)
}
override fun onResponse(call: Call, response: Response) {
continuation.resume(response)
}
})
continuation.invokeOnCancellation {
this.cancel()
}
return call.executeAsync().use { response ->
response.body.byteStream()
}
}
@@ -167,3 +144,38 @@ private class ProgressResponseBody (
}
}
}
// now based on <https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/UploadProgress.java#L84>
class ProgressRequestBody(
private val requestBody: RequestBody,
private val progressCallback: ProgressCallback
) : RequestBody() {
override fun contentType(): MediaType? {
return requestBody.contentType()
}
override fun contentLength(): Long {
return requestBody.contentLength()
}
override fun writeTo(sink: BufferedSink) {
val bufferedSink = this.sink(sink).buffer()
requestBody.writeTo(bufferedSink)
bufferedSink.flush()
}
private fun sink(sink: Sink): Sink {
return object : ForwardingSink(sink) {
var totalBytesWritten = 0L
override fun write(source: Buffer, byteCount: Long) {
super.write(source, byteCount)
totalBytesWritten += byteCount
progressCallback(
totalBytesWritten,
contentLength()
)
}
}
}
}