From 1f1e0de52350f70225395ffc36954ab540d4c669 Mon Sep 17 00:00:00 2001 From: Chloe <25387744+qimiko@users.noreply.github.com> Date: Sun, 1 Feb 2026 01:28:07 -0700 Subject: [PATCH] switch to okio official coroutines --- app/build.gradle.kts | 3 + .../launcher/updater/ReleaseRepository.kt | 169 +++++++++--------- .../com/geode/launcher/utils/DownloadUtils.kt | 74 ++++---- 3 files changed, 131 insertions(+), 115 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a2e3fe42..4c523304 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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") diff --git a/app/src/main/java/com/geode/launcher/updater/ReleaseRepository.kt b/app/src/main/java/com/geode/launcher/updater/ReleaseRepository.kt index 2c3117fd..de79ce8f 100644 --- a/app/src/main/java/com/geode/launcher/updater/ReleaseRepository.kt +++ b/app/src/main/java/com/geode/launcher/updater/ReleaseRepository.kt @@ -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>( + 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>( - 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( + 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( - 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>( + 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>( - response.body!!.source() - ) - - val release = body.payload ?: throw IOException("index error: ${body.error}") - - DownloadableLoaderRelease(release) - } - 404 -> { - null - } - else -> { - throw IOException("unknown response ${response.code}") } } } diff --git a/app/src/main/java/com/geode/launcher/utils/DownloadUtils.kt b/app/src/main/java/com/geode/launcher/utils/DownloadUtils.kt index 2e71f136..de1ec2cd 100644 --- a/app/src/main/java/com/geode/launcher/utils/DownloadUtils.kt +++ b/app/src/main/java/com/geode/launcher/utils/DownloadUtils.kt @@ -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() } } @@ -166,4 +143,39 @@ private class ProgressResponseBody ( } } } +} + +// now based on +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() + ) + } + } + } } \ No newline at end of file