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
|
* Release download link for the current platform
|
||||||
*/
|
*/
|
||||||
abstract fun getDownload(): DownloadableAsset?
|
abstract fun getDownload(): DownloadableAsset?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resources download link for current platform.
|
||||||
|
*/
|
||||||
|
abstract fun getResourcesDownload(): DownloadableAsset?
|
||||||
}
|
}
|
||||||
|
|
||||||
class DownloadableGitHubLoaderRelease(private val release: Release) : Downloadable() {
|
class DownloadableGitHubLoaderRelease(private val release: Release) : Downloadable() {
|
||||||
@@ -101,6 +106,18 @@ class DownloadableGitHubLoaderRelease(private val release: Release) : Downloadab
|
|||||||
size = download.size.toLong()
|
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() {
|
class DownloadableLauncherRelease(val release: Release) : Downloadable() {
|
||||||
@@ -126,8 +143,7 @@ class DownloadableLauncherRelease(val release: Release) : Downloadable() {
|
|||||||
* 0 | 0 | 1
|
* 0 | 0 | 1
|
||||||
* surprise! it's an xnor
|
* surprise! it's an xnor
|
||||||
*/
|
*/
|
||||||
|
asset.name.endsWith("apk") && asset.name.contains("android32") == use32BitPlatform
|
||||||
(asset.name.contains("android32")) == use32BitPlatform
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,6 +155,10 @@ class DownloadableLauncherRelease(val release: Release) : Downloadable() {
|
|||||||
size = download.size.toLong()
|
size = download.size.toLong()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getResourcesDownload(): DownloadableAsset? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DownloadableLoaderRelease(private val version: LoaderVersion) : Downloadable() {
|
class DownloadableLoaderRelease(private val version: LoaderVersion) : Downloadable() {
|
||||||
@@ -151,11 +171,19 @@ class DownloadableLoaderRelease(private val version: LoaderVersion) : Downloadab
|
|||||||
return version.createdAt.epochSeconds
|
return version.createdAt.epochSeconds
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDownload(): DownloadableAsset? {
|
override fun getDownload(): DownloadableAsset {
|
||||||
val filename = "geode-${version.tag}-${LaunchUtils.platformName}.zip"
|
val filename = "geode-${version.tag}-${LaunchUtils.platformName}.zip"
|
||||||
return DownloadableAsset(
|
return DownloadableAsset(
|
||||||
url = "https://github.com/geode-sdk/geode/releases/download/${version.tag}/$filename",
|
url = "https://github.com/geode-sdk/geode/releases/download/${version.tag}/$filename",
|
||||||
filename = 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 okio.source
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
|
import java.io.IOException
|
||||||
import java.io.InterruptedIOException
|
import java.io.InterruptedIOException
|
||||||
|
import kotlin.math.max
|
||||||
import kotlin.time.Clock
|
import kotlin.time.Clock
|
||||||
import kotlin.time.Duration.Companion.minutes
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
|
||||||
@@ -168,6 +170,53 @@ class ReleaseManager private constructor(
|
|||||||
return Result.success(outputFile)
|
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) {
|
private suspend fun performUpdate(release: Downloadable) {
|
||||||
val releaseAsset = release.getDownload()
|
val releaseAsset = release.getDownload()
|
||||||
if (releaseAsset == null) {
|
if (releaseAsset == null) {
|
||||||
@@ -177,8 +226,14 @@ class ReleaseManager private constructor(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val resourcesAsset = release.getResourcesDownload()
|
||||||
|
|
||||||
|
var releaseSize = releaseAsset.size ?: 0L
|
||||||
|
val resourcesSize = resourcesAsset?.size ?: 0L
|
||||||
|
|
||||||
// set an initial download size
|
// 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()
|
val outputFile = getTempFile()
|
||||||
// clone the file instance as renameTo may move the original file
|
// clone the file instance as renameTo may move the original file
|
||||||
@@ -197,7 +252,9 @@ class ReleaseManager private constructor(
|
|||||||
httpClient,
|
httpClient,
|
||||||
releaseAsset.url,
|
releaseAsset.url,
|
||||||
onProgress = { progress, outOf ->
|
onProgress = { progress, outOf ->
|
||||||
_uiState.value = ReleaseManagerState.InDownload(progress, outOf)
|
_uiState.value = ReleaseManagerState.InDownload(progress, outOf + resourcesSize)
|
||||||
|
|
||||||
|
releaseSize = max(outOf, releaseSize)
|
||||||
},
|
},
|
||||||
onResponse = { body ->
|
onResponse = { body ->
|
||||||
DownloadUtils.extractFileFromZipStream(
|
DownloadUtils.extractFileFromZipStream(
|
||||||
@@ -231,6 +288,10 @@ class ReleaseManager private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (resourcesAsset != null) {
|
||||||
|
performResourceDownload(resourcesAsset, releaseSize)
|
||||||
|
}
|
||||||
|
|
||||||
// extraction performed
|
// extraction performed
|
||||||
updatePreferences(release)
|
updatePreferences(release)
|
||||||
|
|
||||||
@@ -251,7 +312,7 @@ class ReleaseManager private constructor(
|
|||||||
val sharedPreferences = PreferenceUtils.get(applicationContext)
|
val sharedPreferences = PreferenceUtils.get(applicationContext)
|
||||||
|
|
||||||
val originalFileHash = sharedPreferences.getString(PreferenceUtils.Key.CURRENT_RELEASE_MODIFIED)
|
val originalFileHash = sharedPreferences.getString(PreferenceUtils.Key.CURRENT_RELEASE_MODIFIED)
|
||||||
?: return false
|
?: return true
|
||||||
|
|
||||||
val currentFileHash = computeFileHash(geodeFile)
|
val currentFileHash = computeFileHash(geodeFile)
|
||||||
return originalFileHash != currentFileHash
|
return originalFileHash != currentFileHash
|
||||||
@@ -401,6 +462,13 @@ class ReleaseManager private constructor(
|
|||||||
return File(geodeDirectory, geodeName)
|
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 {
|
private fun getTempFile(): File {
|
||||||
val geodeName = LaunchUtils.geodeFilename
|
val geodeName = LaunchUtils.geodeFilename
|
||||||
val geodeDirectory = LaunchUtils.getBaseDirectory(applicationContext)
|
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
|
// 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)
|
// 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 = createRandomString()
|
||||||
val suffix = buildString(8) {
|
|
||||||
repeat(8) { append(alphabet.random()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
val tmpName = "tmp-$suffix.$geodeName"
|
val tmpName = "tmp-$suffix.$geodeName"
|
||||||
|
|
||||||
val tempFile = File(geodeDirectory, tmpName)
|
val tempFile = File(geodeDirectory, tmpName)
|
||||||
@@ -420,6 +484,19 @@ class ReleaseManager private constructor(
|
|||||||
return tempFile
|
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.
|
* 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
|
* 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/")
|
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? {
|
fun getLastCrash(context: Context): File? {
|
||||||
val crashDirectory = getCrashDirectory(context)
|
val crashDirectory = getCrashDirectory(context)
|
||||||
if (!crashDirectory.exists()) {
|
if (!crashDirectory.exists()) {
|
||||||
|
|||||||
Reference in new issue
Block a user