forked from geode-sdk/android-launcher
improve resilience of downloadStream
This commit is contained in:
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) {
|
||||
|
||||
Reference in new issue
Block a user