mirror of
https://github.com/geode-sdk/android-launcher.git
synced 2026-10-21 16:32:39 +00:00
perform resources download within launcher
This commit is contained in:
4 files changed
+150
-11
No files matched your search
@@ -62,6 +62,11 @@ abstract class Downloadable {
|
||||
* Release download link for the current platform
|
||||
*/
|
||||
abstract fun getDownload(): DownloadableAsset?
|
||||
|
||||
/**
|
||||
* Resources download link for current platform.
|
||||
*/
|
||||
abstract fun getResourcesDownload(): DownloadableAsset?
|
||||
}
|
||||
|
||||
class DownloadableGitHubLoaderRelease(private val release: Release) : Downloadable() {
|
||||
@@ -101,6 +106,18 @@ class DownloadableGitHubLoaderRelease(private val release: Release) : Downloadab
|
||||
size = download.size.toLong()
|
||||
)
|
||||
}
|
||||
|
||||
override fun getResourcesDownload(): DownloadableAsset? {
|
||||
val download = release.assets.find {
|
||||
it.name == "resources.zip"
|
||||
} ?: return null
|
||||
|
||||
return DownloadableAsset(
|
||||
url = download.browserDownloadUrl,
|
||||
filename = download.name,
|
||||
size = download.size.toLong()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class DownloadableLauncherRelease(val release: Release) : Downloadable() {
|
||||
@@ -126,8 +143,7 @@ class DownloadableLauncherRelease(val release: Release) : Downloadable() {
|
||||
* 0 | 0 | 1
|
||||
* surprise! it's an xnor
|
||||
*/
|
||||
|
||||
(asset.name.contains("android32")) == use32BitPlatform
|
||||
asset.name.endsWith("apk") && asset.name.contains("android32") == use32BitPlatform
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +155,10 @@ class DownloadableLauncherRelease(val release: Release) : Downloadable() {
|
||||
size = download.size.toLong()
|
||||
)
|
||||
}
|
||||
|
||||
override fun getResourcesDownload(): DownloadableAsset? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
class DownloadableLoaderRelease(private val version: LoaderVersion) : Downloadable() {
|
||||
@@ -151,11 +171,19 @@ class DownloadableLoaderRelease(private val version: LoaderVersion) : Downloadab
|
||||
return version.createdAt.epochSeconds
|
||||
}
|
||||
|
||||
override fun getDownload(): DownloadableAsset? {
|
||||
override fun getDownload(): DownloadableAsset {
|
||||
val filename = "geode-${version.tag}-${LaunchUtils.platformName}.zip"
|
||||
return DownloadableAsset(
|
||||
url = "https://github.com/geode-sdk/geode/releases/download/${version.tag}/$filename",
|
||||
filename = filename
|
||||
)
|
||||
}
|
||||
|
||||
override fun getResourcesDownload(): DownloadableAsset {
|
||||
val filename = "resources.zip"
|
||||
return DownloadableAsset(
|
||||
url = "https://github.com/geode-sdk/geode/releases/download/${version.tag}/resources.zip",
|
||||
filename = filename
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,9 @@ import okio.sink
|
||||
import okio.source
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.io.InterruptedIOException
|
||||
import kotlin.math.max
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
@@ -168,6 +170,53 @@ class ReleaseManager private constructor(
|
||||
return Result.success(outputFile)
|
||||
}
|
||||
|
||||
private suspend fun performResourceDownload(resourceAsset: DownloadableAsset, initialSize: Long) {
|
||||
val guessSize = if (resourceAsset.size == null) null else initialSize + resourceAsset.size
|
||||
val guessInitial = if (guessSize == null) 0 else initialSize
|
||||
_uiState.value = ReleaseManagerState.InDownload(guessInitial, guessSize)
|
||||
|
||||
val outputDir = getTempResourcesDirectory()
|
||||
val outputPath = outputDir.path
|
||||
|
||||
val finalDir = LaunchUtils.getGeodeResourcesDirectory(applicationContext)
|
||||
|
||||
try {
|
||||
if (!outputDir.exists()) {
|
||||
outputDir.mkdirs()
|
||||
}
|
||||
|
||||
DownloadUtils.downloadStream(
|
||||
httpClient,
|
||||
resourceAsset.url,
|
||||
onProgress = { progress, outOf ->
|
||||
_uiState.value = ReleaseManagerState.InDownload(initialSize + progress, outOf + initialSize)
|
||||
},
|
||||
onResponse = { body ->
|
||||
DownloadUtils.copyZipStreamToDirectory(
|
||||
body.byteStream(),
|
||||
outputDir
|
||||
)
|
||||
|
||||
if (finalDir.exists()) {
|
||||
finalDir.deleteRecursively()
|
||||
}
|
||||
|
||||
if (!outputDir.renameTo(finalDir)) {
|
||||
throw IOException("failed to rename resources output directory")
|
||||
}
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
sendError(e)
|
||||
return
|
||||
} finally {
|
||||
val tempPathClone = File(outputPath)
|
||||
runCatching {
|
||||
if (tempPathClone.exists()) tempPathClone.deleteRecursively()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun performUpdate(release: Downloadable) {
|
||||
val releaseAsset = release.getDownload()
|
||||
if (releaseAsset == null) {
|
||||
@@ -177,8 +226,14 @@ class ReleaseManager private constructor(
|
||||
return
|
||||
}
|
||||
|
||||
val resourcesAsset = release.getResourcesDownload()
|
||||
|
||||
var releaseSize = releaseAsset.size ?: 0L
|
||||
val resourcesSize = resourcesAsset?.size ?: 0L
|
||||
|
||||
// set an initial download size
|
||||
_uiState.value = ReleaseManagerState.InDownload(0, releaseAsset.size)
|
||||
val initialSize = if (releaseAsset.size == null && resourcesAsset?.size == null) null else releaseSize + resourcesSize
|
||||
_uiState.value = ReleaseManagerState.InDownload(0, initialSize)
|
||||
|
||||
val outputFile = getTempFile()
|
||||
// clone the file instance as renameTo may move the original file
|
||||
@@ -197,7 +252,9 @@ class ReleaseManager private constructor(
|
||||
httpClient,
|
||||
releaseAsset.url,
|
||||
onProgress = { progress, outOf ->
|
||||
_uiState.value = ReleaseManagerState.InDownload(progress, outOf)
|
||||
_uiState.value = ReleaseManagerState.InDownload(progress, outOf + resourcesSize)
|
||||
|
||||
releaseSize = max(outOf, releaseSize)
|
||||
},
|
||||
onResponse = { body ->
|
||||
DownloadUtils.extractFileFromZipStream(
|
||||
@@ -231,6 +288,10 @@ class ReleaseManager private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
if (resourcesAsset != null) {
|
||||
performResourceDownload(resourcesAsset, releaseSize)
|
||||
}
|
||||
|
||||
// extraction performed
|
||||
updatePreferences(release)
|
||||
|
||||
@@ -251,7 +312,7 @@ class ReleaseManager private constructor(
|
||||
val sharedPreferences = PreferenceUtils.get(applicationContext)
|
||||
|
||||
val originalFileHash = sharedPreferences.getString(PreferenceUtils.Key.CURRENT_RELEASE_MODIFIED)
|
||||
?: return false
|
||||
?: return true
|
||||
|
||||
val currentFileHash = computeFileHash(geodeFile)
|
||||
return originalFileHash != currentFileHash
|
||||
@@ -401,6 +462,13 @@ class ReleaseManager private constructor(
|
||||
return File(geodeDirectory, geodeName)
|
||||
}
|
||||
|
||||
private fun createRandomString(): String {
|
||||
val alphabet = ('A'..'Z') + ('a'..'z') + ('0'..'9')
|
||||
return buildString(8) {
|
||||
repeat(8) { append(alphabet.random()) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTempFile(): File {
|
||||
val geodeName = LaunchUtils.geodeFilename
|
||||
val geodeDirectory = LaunchUtils.getBaseDirectory(applicationContext)
|
||||
@@ -408,11 +476,7 @@ class ReleaseManager private constructor(
|
||||
// warning!! while File::createTempFile may look tempting, a certain brand of phones has a messed up implementation of it
|
||||
// so we're making a temp file manually (as long as it doesn't collide with the geode download, it's okay)
|
||||
|
||||
val alphabet = ('A'..'Z') + ('a'..'z') + ('0'..'9')
|
||||
val suffix = buildString(8) {
|
||||
repeat(8) { append(alphabet.random()) }
|
||||
}
|
||||
|
||||
val suffix = createRandomString()
|
||||
val tmpName = "tmp-$suffix.$geodeName"
|
||||
|
||||
val tempFile = File(geodeDirectory, tmpName)
|
||||
@@ -420,6 +484,19 @@ class ReleaseManager private constructor(
|
||||
return tempFile
|
||||
}
|
||||
|
||||
private fun getTempResourcesDirectory(): File {
|
||||
val finalDir = LaunchUtils.getGeodeResourcesDirectory(applicationContext)
|
||||
val suffix = createRandomString()
|
||||
val tempDirName = "tmp-$suffix-geode.loader"
|
||||
|
||||
val parentDir = finalDir.parentFile
|
||||
return if (parentDir != null) {
|
||||
File(finalDir.parentFile, tempDirName)
|
||||
} else {
|
||||
File(tempDirName)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the current update job.
|
||||
*/
|
||||
|
||||
@@ -72,6 +72,35 @@ object DownloadUtils {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun copyZipStreamToDirectory(inputStream: InputStream, output: File) = runInterruptible {
|
||||
ZipInputStream(inputStream).use { zipStream ->
|
||||
if (output.exists()) output.deleteRecursively()
|
||||
output.mkdirs()
|
||||
|
||||
val canonicalOutput = output.canonicalPath
|
||||
|
||||
var entry = zipStream.nextEntry
|
||||
while (entry != null) {
|
||||
val destination = File(output, entry.name)
|
||||
if (!destination.canonicalPath.startsWith(canonicalOutput)) {
|
||||
throw IOException("attempted copy to outside of output directory: ${entry.name}")
|
||||
}
|
||||
|
||||
if (destination.isDirectory) {
|
||||
destination.mkdirs()
|
||||
} else {
|
||||
destination.parentFile?.mkdirs()
|
||||
|
||||
destination.outputStream().use { destinationStream ->
|
||||
zipStream.copyTo(destinationStream)
|
||||
}
|
||||
}
|
||||
|
||||
entry = zipStream.nextEntry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a file named by zipPath from inputStream and copies it to outputStream
|
||||
*
|
||||
|
||||
@@ -85,6 +85,11 @@ object LaunchUtils {
|
||||
return File(base, "game/geode/logs/")
|
||||
}
|
||||
|
||||
fun getGeodeResourcesDirectory(context: Context): File {
|
||||
val base = getBaseDirectory(context)
|
||||
return File(base, "game/geode/resources/geode.loader/")
|
||||
}
|
||||
|
||||
fun getLastCrash(context: Context): File? {
|
||||
val crashDirectory = getCrashDirectory(context)
|
||||
if (!crashDirectory.exists()) {
|
||||
|
||||
Reference in new issue
Block a user