Skip to content

Commit 23ed997

Browse files
committed
perform resources download within launcher
1 parent acaff15 commit 23ed997

4 files changed

Lines changed: 150 additions & 11 deletions

File tree

app/src/main/java/com/geode/launcher/updater/Release.kt

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ abstract class Downloadable {
6262
* Release download link for the current platform
6363
*/
6464
abstract fun getDownload(): DownloadableAsset?
65+
66+
/**
67+
* Resources download link for current platform.
68+
*/
69+
abstract fun getResourcesDownload(): DownloadableAsset?
6570
}
6671

6772
class DownloadableGitHubLoaderRelease(private val release: Release) : Downloadable() {
@@ -101,6 +106,18 @@ class DownloadableGitHubLoaderRelease(private val release: Release) : Downloadab
101106
size = download.size.toLong()
102107
)
103108
}
109+
110+
override fun getResourcesDownload(): DownloadableAsset? {
111+
val download = release.assets.find {
112+
it.name == "resources.zip"
113+
} ?: return null
114+
115+
return DownloadableAsset(
116+
url = download.browserDownloadUrl,
117+
filename = download.name,
118+
size = download.size.toLong()
119+
)
120+
}
104121
}
105122

106123
class DownloadableLauncherRelease(val release: Release) : Downloadable() {
@@ -126,8 +143,7 @@ class DownloadableLauncherRelease(val release: Release) : Downloadable() {
126143
* 0 | 0 | 1
127144
* surprise! it's an xnor
128145
*/
129-
130-
(asset.name.contains("android32")) == use32BitPlatform
146+
asset.name.endsWith("apk") && asset.name.contains("android32") == use32BitPlatform
131147
}
132148
}
133149

@@ -139,6 +155,10 @@ class DownloadableLauncherRelease(val release: Release) : Downloadable() {
139155
size = download.size.toLong()
140156
)
141157
}
158+
159+
override fun getResourcesDownload(): DownloadableAsset? {
160+
return null
161+
}
142162
}
143163

144164
class DownloadableLoaderRelease(private val version: LoaderVersion) : Downloadable() {
@@ -151,11 +171,19 @@ class DownloadableLoaderRelease(private val version: LoaderVersion) : Downloadab
151171
return version.createdAt.epochSeconds
152172
}
153173

154-
override fun getDownload(): DownloadableAsset? {
174+
override fun getDownload(): DownloadableAsset {
155175
val filename = "geode-${version.tag}-${LaunchUtils.platformName}.zip"
156176
return DownloadableAsset(
157177
url = "https://github.com/geode-sdk/geode/releases/download/${version.tag}/$filename",
158178
filename = filename
159179
)
160180
}
181+
182+
override fun getResourcesDownload(): DownloadableAsset {
183+
val filename = "resources.zip"
184+
return DownloadableAsset(
185+
url = "https://github.com/geode-sdk/geode/releases/download/${version.tag}/resources.zip",
186+
filename = filename
187+
)
188+
}
161189
}

app/src/main/java/com/geode/launcher/updater/ReleaseManager.kt

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ import okio.sink
2525
import okio.source
2626
import java.io.File
2727
import java.io.FileNotFoundException
28+
import java.io.IOException
2829
import java.io.InterruptedIOException
30+
import kotlin.math.max
2931
import kotlin.time.Clock
3032
import kotlin.time.Duration.Companion.minutes
3133

@@ -168,6 +170,53 @@ class ReleaseManager private constructor(
168170
return Result.success(outputFile)
169171
}
170172

173+
private suspend fun performResourceDownload(resourceAsset: DownloadableAsset, initialSize: Long) {
174+
val guessSize = if (resourceAsset.size == null) null else initialSize + resourceAsset.size
175+
val guessInitial = if (guessSize == null) 0 else initialSize
176+
_uiState.value = ReleaseManagerState.InDownload(guessInitial, guessSize)
177+
178+
val outputDir = getTempResourcesDirectory()
179+
val outputPath = outputDir.path
180+
181+
val finalDir = LaunchUtils.getGeodeResourcesDirectory(applicationContext)
182+
183+
try {
184+
if (!outputDir.exists()) {
185+
outputDir.mkdirs()
186+
}
187+
188+
DownloadUtils.downloadStream(
189+
httpClient,
190+
resourceAsset.url,
191+
onProgress = { progress, outOf ->
192+
_uiState.value = ReleaseManagerState.InDownload(initialSize + progress, outOf + initialSize)
193+
},
194+
onResponse = { body ->
195+
DownloadUtils.copyZipStreamToDirectory(
196+
body.byteStream(),
197+
outputDir
198+
)
199+
200+
if (finalDir.exists()) {
201+
finalDir.deleteRecursively()
202+
}
203+
204+
if (!outputDir.renameTo(finalDir)) {
205+
throw IOException("failed to rename resources output directory")
206+
}
207+
}
208+
)
209+
} catch (e: Exception) {
210+
sendError(e)
211+
return
212+
} finally {
213+
val tempPathClone = File(outputPath)
214+
runCatching {
215+
if (tempPathClone.exists()) tempPathClone.deleteRecursively()
216+
}
217+
}
218+
}
219+
171220
private suspend fun performUpdate(release: Downloadable) {
172221
val releaseAsset = release.getDownload()
173222
if (releaseAsset == null) {
@@ -177,8 +226,14 @@ class ReleaseManager private constructor(
177226
return
178227
}
179228

229+
val resourcesAsset = release.getResourcesDownload()
230+
231+
var releaseSize = releaseAsset.size ?: 0L
232+
val resourcesSize = resourcesAsset?.size ?: 0L
233+
180234
// set an initial download size
181-
_uiState.value = ReleaseManagerState.InDownload(0, releaseAsset.size)
235+
val initialSize = if (releaseAsset.size == null && resourcesAsset?.size == null) null else releaseSize + resourcesSize
236+
_uiState.value = ReleaseManagerState.InDownload(0, initialSize)
182237

183238
val outputFile = getTempFile()
184239
// clone the file instance as renameTo may move the original file
@@ -197,7 +252,9 @@ class ReleaseManager private constructor(
197252
httpClient,
198253
releaseAsset.url,
199254
onProgress = { progress, outOf ->
200-
_uiState.value = ReleaseManagerState.InDownload(progress, outOf)
255+
_uiState.value = ReleaseManagerState.InDownload(progress, outOf + resourcesSize)
256+
257+
releaseSize = max(outOf, releaseSize)
201258
},
202259
onResponse = { body ->
203260
DownloadUtils.extractFileFromZipStream(
@@ -231,6 +288,10 @@ class ReleaseManager private constructor(
231288
}
232289
}
233290

291+
if (resourcesAsset != null) {
292+
performResourceDownload(resourcesAsset, releaseSize)
293+
}
294+
234295
// extraction performed
235296
updatePreferences(release)
236297

@@ -251,7 +312,7 @@ class ReleaseManager private constructor(
251312
val sharedPreferences = PreferenceUtils.get(applicationContext)
252313

253314
val originalFileHash = sharedPreferences.getString(PreferenceUtils.Key.CURRENT_RELEASE_MODIFIED)
254-
?: return false
315+
?: return true
255316

256317
val currentFileHash = computeFileHash(geodeFile)
257318
return originalFileHash != currentFileHash
@@ -401,25 +462,41 @@ class ReleaseManager private constructor(
401462
return File(geodeDirectory, geodeName)
402463
}
403464

465+
private fun createRandomString(): String {
466+
val alphabet = ('A'..'Z') + ('a'..'z') + ('0'..'9')
467+
return buildString(8) {
468+
repeat(8) { append(alphabet.random()) }
469+
}
470+
}
471+
404472
private fun getTempFile(): File {
405473
val geodeName = LaunchUtils.geodeFilename
406474
val geodeDirectory = LaunchUtils.getBaseDirectory(applicationContext)
407475

408476
// warning!! while File::createTempFile may look tempting, a certain brand of phones has a messed up implementation of it
409477
// so we're making a temp file manually (as long as it doesn't collide with the geode download, it's okay)
410478

411-
val alphabet = ('A'..'Z') + ('a'..'z') + ('0'..'9')
412-
val suffix = buildString(8) {
413-
repeat(8) { append(alphabet.random()) }
414-
}
415-
479+
val suffix = createRandomString()
416480
val tmpName = "tmp-$suffix.$geodeName"
417481

418482
val tempFile = File(geodeDirectory, tmpName)
419483

420484
return tempFile
421485
}
422486

487+
private fun getTempResourcesDirectory(): File {
488+
val finalDir = LaunchUtils.getGeodeResourcesDirectory(applicationContext)
489+
val suffix = createRandomString()
490+
val tempDirName = "tmp-$suffix-geode.loader"
491+
492+
val parentDir = finalDir.parentFile
493+
return if (parentDir != null) {
494+
File(finalDir.parentFile, tempDirName)
495+
} else {
496+
File(tempDirName)
497+
}
498+
}
499+
423500
/**
424501
* Cancels the current update job.
425502
*/

app/src/main/java/com/geode/launcher/utils/DownloadUtils.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,35 @@ object DownloadUtils {
7272
}
7373
}
7474

75+
suspend fun copyZipStreamToDirectory(inputStream: InputStream, output: File) = runInterruptible {
76+
ZipInputStream(inputStream).use { zipStream ->
77+
if (output.exists()) output.deleteRecursively()
78+
output.mkdirs()
79+
80+
val canonicalOutput = output.canonicalPath
81+
82+
var entry = zipStream.nextEntry
83+
while (entry != null) {
84+
val destination = File(output, entry.name)
85+
if (!destination.canonicalPath.startsWith(canonicalOutput)) {
86+
throw IOException("attempted copy to outside of output directory: ${entry.name}")
87+
}
88+
89+
if (destination.isDirectory) {
90+
destination.mkdirs()
91+
} else {
92+
destination.parentFile?.mkdirs()
93+
94+
destination.outputStream().use { destinationStream ->
95+
zipStream.copyTo(destinationStream)
96+
}
97+
}
98+
99+
entry = zipStream.nextEntry
100+
}
101+
}
102+
}
103+
75104
/**
76105
* Extracts a file named by zipPath from inputStream and copies it to outputStream
77106
*

app/src/main/java/com/geode/launcher/utils/LaunchUtils.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ object LaunchUtils {
8585
return File(base, "game/geode/logs/")
8686
}
8787

88+
fun getGeodeResourcesDirectory(context: Context): File {
89+
val base = getBaseDirectory(context)
90+
return File(base, "game/geode/resources/geode.loader/")
91+
}
92+
8893
fun getLastCrash(context: Context): File? {
8994
val crashDirectory = getCrashDirectory(context)
9095
if (!crashDirectory.exists()) {

0 commit comments

Comments
 (0)