improve resilience of downloadStream

This commit is contained in:
Chloe committed 2026-02-24 04:13:59 -07:00
1 parent d1fdb26d44
commit 3202851303
2 files changed
+77 -31

No files matched your search

@@ -19,6 +19,7 @@ import kotlinx.coroutines.launch
import okhttp3.Cache
import okhttp3.OkHttpClient
import okio.buffer
import okio.sink
import okio.source
import java.io.File
import java.io.FileNotFoundException
@@ -149,12 +150,15 @@ class ReleaseManager private constructor(
try {
DownloadUtils.downloadStream(
httpClient,
download.url
).use { fileStream ->
outputFile.outputStream().use {
fileStream.copyTo(it)
download.url,
onResponse = { body ->
body.source().use { source ->
outputFile.sink().buffer().use { sink ->
sink.writeAll(source)
}
}
}
}
)
} catch (e: Exception) {
return Result.failure(e)
}
@@ -179,37 +183,49 @@ class ReleaseManager private constructor(
val tempFilePath = outputFile.path
try {
val fileStream = DownloadUtils.downloadStream(
httpClient,
releaseAsset.url
) { progress, outOf ->
_uiState.value = ReleaseManagerState.InDownload(progress, outOf)
}
outputFile.parentFile?.mkdirs()
val geodeFile = getGeodeOutputPath()
DownloadUtils.extractFileFromZipStream(
fileStream,
outputFile.outputStream(),
geodeFile.name
)
// work around a permission issue from adb push
if (geodeFile.exists()) {
geodeFile.delete()
val geodeParent = geodeFile.parentFile
if (geodeParent != null && !geodeParent.exists()) {
geodeParent.mkdirs()
}
outputFile.renameTo(geodeFile)
DownloadUtils.downloadStream(
httpClient,
releaseAsset.url,
onProgress = { progress, outOf ->
_uiState.value = ReleaseManagerState.InDownload(progress, outOf)
},
onResponse = { body ->
DownloadUtils.extractFileFromZipStream(
body.byteStream(),
outputFile.outputStream(),
geodeFile.name
)
// work around a permission issue from adb push
if (geodeFile.exists()) {
geodeFile.delete()
}
val renameSuccessful = runCatching {
outputFile.renameTo(geodeFile)
}.getOrDefault(false)
if (!renameSuccessful) {
// attempt a manual copy if rename fails for whatever reason
DownloadUtils.copyFile(outputFile.inputStream(), geodeFile.outputStream())
}
}
)
} catch (e: Exception) {
sendError(e)
return
} finally {
val tempFileClone = File(tempFilePath)
try {
runCatching {
if (tempFileClone.exists()) tempFileClone.delete()
} catch (_: Exception) { }
}
}
// extraction performed
@@ -2,7 +2,9 @@ package com.geode.launcher.utils
import android.os.Build
import android.os.FileUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.withContext
import okhttp3.MediaType
import okhttp3.OkHttpClient
import okhttp3.Request
@@ -12,11 +14,14 @@ import okhttp3.coroutines.executeAsync
import okio.Buffer
import okio.BufferedSink
import okio.BufferedSource
import okio.FileSystem
import okio.ForwardingSink
import okio.ForwardingSource
import okio.Path.Companion.toOkioPath
import okio.Sink
import okio.Source
import okio.buffer
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
@@ -30,8 +35,9 @@ object DownloadUtils {
suspend fun downloadStream(
httpClient: OkHttpClient,
url: String,
onProgress: ProgressCallback? = null
): InputStream {
onProgress: ProgressCallback? = null,
onResponse: suspend (ResponseBody) -> Unit,
) {
val request = Request.Builder()
.url(url)
.build()
@@ -58,11 +64,19 @@ object DownloadUtils {
val progressClient = progressClientBuilder.build()
val call = progressClient.newCall(request)
val response = call.executeAsync()
return response.body.byteStream()
call.executeAsync().use { response ->
when (response.code) {
200 -> onResponse(response.body)
else -> throw IOException("unexpected response ${response.code}")
}
}
}
/**
* Extracts a file named by zipPath from inputStream and copies it to outputStream
*
* inputStream and outputStream are both closed after this function executes
*/
suspend fun extractFileFromZipStream(inputStream: InputStream, outputStream: OutputStream, zipPath: String) {
// note to self: ZipInputStreams are a little silly
// (runInterruptible allows it to cancel, otherwise it waits for the stream to finish)
@@ -90,6 +104,22 @@ object DownloadUtils {
}
}
suspend fun copyFile(from: File, to: File) {
val fs = FileSystem.SYSTEM
val fromPath = from.toOkioPath()
val toPath = to.toOkioPath()
withContext(Dispatchers.IO) {
fs.copy(fromPath, toPath)
}
}
/**
* Copies data from inputStream to outputStream
*
* Both streams are closed upon the conclusion of this function.
*/
fun copyFile(inputStream: InputStream, outputStream: OutputStream) {
// gotta love copying
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {