Skip to content

Commit

Permalink
make updater abstract
Browse files Browse the repository at this point in the history
  • Loading branch information
qimiko committed Jan 18, 2025
1 parent b409a20 commit a687f84
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 66 deletions.
10 changes: 6 additions & 4 deletions app/src/main/java/com/geode/launcher/AltMainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -185,16 +185,18 @@ fun mapLaunchStatusToInfo(state: LaunchViewModel.LaunchUIState, inSafeMode: Bool
}

val outOf = remember(state.outOf) {
Formatter.formatShortFileSize(context, state.outOf)
state.outOf?.apply {
Formatter.formatShortFileSize(context, this)
}
}

LaunchStatusInfo(
title = stringResource(R.string.launcher_downloading_update),
details = stringResource(R.string.launcher_downloading_update_details, downloaded, outOf),
details = stringResource(R.string.launcher_downloading_update_details, downloaded, outOf ?: ""),
progress = {
val progress = state.downloaded / state.outOf.toDouble()
val progress = state.downloaded / state.outOf!!.toDouble()
progress.toFloat()
}
}.takeIf { state.outOf != null }
)
}
is LaunchViewModel.LaunchUIState.Cancelled -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class LaunchViewModel(private val application: Application): ViewModel() {
sealed class LaunchUIState {
data object Initial : LaunchUIState()
data object UpdateCheck : LaunchUIState()
data class Updating(val downloaded: Long, val outOf: Long) : LaunchUIState()
data class Updating(val downloaded: Long, val outOf: Long?) : LaunchUIState()
data class Cancelled(val reason: LaunchCancelReason, val inProgress: Boolean = false) : LaunchUIState()
data object Working : LaunchUIState()
data object Ready : LaunchUIState()
Expand Down
14 changes: 8 additions & 6 deletions app/src/main/java/com/geode/launcher/main/UpdateComponents.kt
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ fun generateInstallIntent(uri: Uri): Intent {

fun installLauncherUpdate(context: Context) {
val nextUpdate = ReleaseManager.get(context).availableLauncherUpdate.value
val launcherDownload = nextUpdate?.getLauncherDownload()
val launcherDownload = nextUpdate?.getDownload()

if (launcherDownload != null) {
val outputFile = launcherDownload.name
val outputFile = launcherDownload.filename
val baseDirectory = LaunchUtils.getBaseDirectory(context)

val outputPathFile = File(baseDirectory, outputFile)
Expand Down Expand Up @@ -237,21 +237,23 @@ fun UpdateCard(releaseViewModel: ReleaseViewModel, modifier: Modifier = Modifier
}

val outOf = remember(state.outOf) {
Formatter.formatShortFileSize(context, state.outOf)
state.outOf?.run {
Formatter.formatShortFileSize(context, this)
}
}

UpdateProgressIndicator(
stringResource(
R.string.release_fetch_downloading,
downloaded,
outOf
outOf ?: ""
),
modifier = modifier,
releaseViewModel = releaseViewModel,
progress = {
val progress = state.downloaded / state.outOf.toDouble()
val progress = state.downloaded / state.outOf!!.toDouble()
progress.toFloat()
}
}.takeIf { state.outOf != null }
)
}
is ReleaseViewModel.ReleaseUIState.InUpdateCheck -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,16 @@ fun UpdateIndicator(
is ReleaseViewModel.ReleaseUIState.InDownload -> {
// is this the ideal design? idk
enablePopup = true
val progress = updateStatus.downloaded / updateStatus.outOf.toDouble()

CircularProgressIndicator(
progress = { progress.toFloat() },
)
if (updateStatus.outOf != null) {
CircularProgressIndicator(
progress = {
val progress = updateStatus.downloaded / updateStatus.outOf.toDouble()
progress.toFloat()
},
)
} else {
CircularProgressIndicator()
}
}
else -> {}
}
Expand Down
95 changes: 83 additions & 12 deletions app/src/main/java/com/geode/launcher/updater/Release.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ data class Asset(
)

@Serializable
class Release(
data class Release(
val url: String,
val id: Int,
val targetCommitish: String,
Expand All @@ -25,39 +25,83 @@ class Release(
val publishedAt: Instant,
val assets: List<Asset>,
val htmlUrl: String
) {
fun getDescription(): String {
if (tagName == "nightly") {
)

@Serializable
data class LoaderVersion(
val tag: String,
val version: String,
val createdAt: Instant,
val commitHash: String,
val prerelease: Boolean
)

@Serializable
data class LoaderPayload<T>(
val payload: T?,
val error: String
)

data class DownloadableAsset(val url: String, val filename: String, val size: Long? = null)

abstract class Downloadable {
abstract fun getDescription(): String
abstract fun getDescriptor(): Long
abstract fun getDownload(): DownloadableAsset?
}

class DownloadableGitHubLoaderRelease(private val release: Release) : Downloadable() {
override fun getDescription(): String {
if (release.tagName == "nightly") {
// get the commit from the assets
// otherwise, a separate request is needed to get the hash (ew)
val asset = assets.first()
val asset = release.assets.first()
val commit = asset.name.substring(6..12)

return "nightly-$commit"
}

return tagName
return release.tagName
}

fun getDescriptor(): Long {
return createdAt.epochSeconds
override fun getDescriptor(): Long {
return release.createdAt.epochSeconds
}

fun getGeodeDownload(): Asset? {
private fun getGitHubDownload(): Asset? {
// try to find an asset that matches the architecture first
val platform = LaunchUtils.platformName

val releaseSuffix = "$platform.zip"
return assets.find {
return release.assets.find {
it.name.endsWith(releaseSuffix)
}
}

fun getLauncherDownload(): Asset? {
override fun getDownload(): DownloadableAsset? {
val download = getGitHubDownload() ?: return null
return DownloadableAsset(
url = download.browserDownloadUrl,
filename = download.name,
size = download.size.toLong()
)
}
}

class DownloadableLauncherRelease(val release: Release) : Downloadable() {
override fun getDescription(): String {
return release.tagName
}

override fun getDescriptor(): Long {
return release.createdAt.epochSeconds
}

private fun getGitHubDownload(): Asset? {
val platform = LaunchUtils.platformName
val use32BitPlatform = platform == "android32"

return assets.find { asset ->
return release.assets.find { asset ->
/* you know it's good when you pull out the truth table
* u32bp | contains | found
* 1 | 1 | 1
Expand All @@ -70,4 +114,31 @@ class Release(
(asset.name.contains("android32")) == use32BitPlatform
}
}

override fun getDownload(): DownloadableAsset? {
val download = getGitHubDownload() ?: return null
return DownloadableAsset(
url = download.browserDownloadUrl,
filename = download.name,
size = download.size.toLong()
)
}
}

class DownloadableLoaderRelease(private val version: LoaderVersion) : Downloadable() {
override fun getDescription(): String {
return version.tag
}

override fun getDescriptor(): Long {
return version.createdAt.epochSeconds
}

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
)
}
}
51 changes: 24 additions & 27 deletions app/src/main/java/com/geode/launcher/updater/ReleaseManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class ReleaseManager private constructor(
sealed class ReleaseManagerState {
data object InUpdateCheck : ReleaseManagerState()
data class Failure(val exception: Exception) : ReleaseManagerState()
data class InDownload(val downloaded: Long, val outOf: Long) : ReleaseManagerState()
data class InDownload(val downloaded: Long, val outOf: Long?) : ReleaseManagerState()
data class Finished(val hasUpdated: Boolean = false) : ReleaseManagerState()
}

Expand All @@ -82,7 +82,7 @@ class ReleaseManager private constructor(
val isInUpdate: Boolean
get() = _uiState.value !is ReleaseManagerState.Failure && _uiState.value !is ReleaseManagerState.Finished

private val _availableLauncherUpdate = MutableStateFlow<Release?>(null)
private val _availableLauncherUpdate = MutableStateFlow<DownloadableLauncherRelease?>(null)
val availableLauncherUpdate = _availableLauncherUpdate.asStateFlow()

private fun sendError(e: Exception) {
Expand All @@ -106,24 +106,21 @@ class ReleaseManager private constructor(
}
}

private fun getBestReleaseForGameVersion(): String? {
private fun getBestReleaseForGameVersion(gameVersion: Long): String? = when {
gameVersion >= 40L -> mapSelectedReleaseToTag()
gameVersion == 39L -> "v3.9.2"
gameVersion == 38L -> "v2.0.0-beta.27"
gameVersion == 37L -> "v2.0.0-beta.4"
else -> null
}

private suspend fun getLatestRelease(): Downloadable? {
if (!GamePackageUtils.isGameInstalled(applicationContext.packageManager)) {
return null
}

val gameVersion = GamePackageUtils.getGameVersionCode(applicationContext.packageManager)

return when {
gameVersion >= 40L -> mapSelectedReleaseToTag()
gameVersion == 39L -> "v3.9.2"
gameVersion == 38L -> "v2.0.0-beta.27"
gameVersion == 37L -> "v2.0.0-beta.4"
else -> null
}
}

private suspend fun getLatestRelease(): Release? {
val targetTag = getBestReleaseForGameVersion() ?: return null
val targetTag = getBestReleaseForGameVersion(gameVersion) ?: return null

return when (targetTag) {
TAG_LATEST -> releaseRepository.getLatestGeodeRelease()
Expand All @@ -132,23 +129,23 @@ class ReleaseManager private constructor(
}
}

private suspend fun downloadLauncherUpdate(release: Release) {
val download = release.getLauncherDownload() ?: return
private suspend fun downloadLauncherUpdate(release: Downloadable) {
val download = release.getDownload() ?: return

val outputDirectory = LaunchUtils.getBaseDirectory(applicationContext)
val outputFile = File(outputDirectory, download.name)
val outputFile = File(outputDirectory, download.filename)

if (outputFile.exists()) {
// only download the apk once
return
}

_uiState.value = ReleaseManagerState.InDownload(0, download.size.toLong())
_uiState.value = ReleaseManagerState.InDownload(0, download.size)

try {
val fileStream = DownloadUtils.downloadStream(
httpClient,
download.browserDownloadUrl
download.url
) { progress, outOf ->
_uiState.value = ReleaseManagerState.InDownload(progress, outOf)
}
Expand All @@ -160,8 +157,8 @@ class ReleaseManager private constructor(
}
}

private suspend fun performUpdate(release: Release) {
val releaseAsset = release.getGeodeDownload()
private suspend fun performUpdate(release: Downloadable) {
val releaseAsset = release.getDownload()
if (releaseAsset == null) {
val noAssetException = Exception("missing Android download")
_uiState.value = ReleaseManagerState.Failure(noAssetException)
Expand All @@ -170,12 +167,12 @@ class ReleaseManager private constructor(
}

// set an initial download size
_uiState.value = ReleaseManagerState.InDownload(0, releaseAsset.size.toLong())
_uiState.value = ReleaseManagerState.InDownload(0, releaseAsset.size)

try {
val fileStream = DownloadUtils.downloadStream(
httpClient,
releaseAsset.browserDownloadUrl
releaseAsset.url
) { progress, outOf ->
_uiState.value = ReleaseManagerState.InDownload(progress, outOf)
}
Expand Down Expand Up @@ -223,8 +220,8 @@ class ReleaseManager private constructor(
return originalFileHash != currentFileHash
}

private fun checkLauncherUpdate(launcherUpdate: Release) {
if (launcherUpdate.tagName != BuildConfig.VERSION_NAME) {
private fun checkLauncherUpdate(launcherUpdate: DownloadableLauncherRelease) {
if (launcherUpdate.release.tagName != BuildConfig.VERSION_NAME) {
_availableLauncherUpdate.value = launcherUpdate
}
}
Expand Down Expand Up @@ -286,7 +283,7 @@ class ReleaseManager private constructor(
performUpdate(release)
}

private fun updatePreferences(release: Release) {
private fun updatePreferences(release: Downloadable) {
val sharedPreferences = PreferenceUtils.get(applicationContext)

sharedPreferences.setString(
Expand Down
Loading

0 comments on commit a687f84

Please sign in to comment.