feat(Updater): handle failures properly

This commit is contained in:
rushiiMachine 2025-06-20 22:50:52 -07:00
parent dd29d5de2f
commit 197bb84c21
No known key found for this signature in database
GPG key ID: DCBE5952BB3B6420
5 changed files with 61 additions and 48 deletions

View file

@ -42,7 +42,6 @@ class ManagerApplication : Application() {
// Services
modules(module {
singleOf(::HttpService)
singleOf(::GithubService)
singleOf(::AliucordGithubService)
singleOf(::AliucordMavenService)
})

View file

@ -1,14 +1,14 @@
package com.aliucord.manager.network.services
import com.aliucord.manager.network.models.BuildInfo
import com.aliucord.manager.network.models.GithubRelease
import com.aliucord.manager.network.utils.ApiResponse
import io.ktor.client.request.header
import io.ktor.client.request.url
import io.ktor.http.HttpHeaders
class AliucordGithubService(
val github: GithubService,
val http: HttpService,
private val http: HttpService,
) {
/**
* Fetches the build data of Aliucord (excluding Aliuhook).
@ -22,7 +22,15 @@ class AliucordGithubService(
}
}
suspend fun getManagerReleases() = github.getReleases(ORG, MANAGER_REPO)
/**
* Fetches all the Manager releases with a 60s local cache.
*/
suspend fun getManagerReleases(): ApiResponse<List<GithubRelease>> {
return http.request {
url("https://api.github.com/repos/rushiiMachine/$MANAGER_REPO/releases")
header(HttpHeaders.CacheControl, "public, max-age=60, s-maxage=60")
}
}
companion object {
const val ORG = "Aliucord"

View file

@ -1,19 +0,0 @@
package com.aliucord.manager.network.services
import com.aliucord.manager.network.models.GithubRelease
import com.aliucord.manager.network.utils.ApiResponse
import io.ktor.client.request.url
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class GithubService(
private val http: HttpService,
) {
suspend fun getReleases(owner: String, repo: String): ApiResponse<List<GithubRelease>> {
return withContext(Dispatchers.IO) {
http.request {
url("https://api.github.com/repos/$owner/$repo/releases")
}
}
}
}

View file

@ -1,17 +1,19 @@
package com.aliucord.manager.ui.widgets.updater
import android.app.Application
import android.util.Log
import androidx.compose.runtime.*
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aliucord.manager.BuildConfig
import com.aliucord.manager.R
import com.aliucord.manager.manager.InstallerManager
import com.aliucord.manager.manager.InstallerSetting
import com.aliucord.manager.manager.download.KtorDownloadManager
import com.aliucord.manager.network.services.AliucordGithubService
import com.aliucord.manager.network.utils.SemVer
import com.aliucord.manager.network.utils.getOrNull
import kotlinx.coroutines.Dispatchers
import com.aliucord.manager.network.utils.getOrThrow
import com.aliucord.manager.util.showToast
import kotlinx.coroutines.launch
class UpdaterViewModel(
@ -29,6 +31,17 @@ class UpdaterViewModel(
private var targetApkUrl: String? = null
init {
viewModelScope.launch {
try {
fetchInfo()
} catch (t: Throwable) {
Log.e(BuildConfig.TAG, "Failed to check for updates!", t)
application.showToast(R.string.updater_fail)
}
}
}
fun dismissDialog() {
showDialog = false
}
@ -52,34 +65,45 @@ class UpdaterViewModel(
}
}
// This updates to the latest release based on the tag name
// It finds all releases that have an asset named aliucord-manager-[tagname].apk (this is in case the filename changes in the future)
// Then compares the semantic versioning of the tag name which should be in the v1.0.0 format
// Finds the latest and downloads/installs it
init {
viewModelScope.launch(Dispatchers.IO) {
val (version, asset) = github.getManagerReleases().getOrNull()
?.mapNotNull { release ->
val version = SemVer.parseOrNull(release.tagName)
?: return@mapNotNull null
/**
* This fetched the releases data from GitHub and populates the state if there is an update.
*
* It obtains all the releases that have an asset named `aliucord-manager-[tag].apk`,
* then finds the latest release based on the largest semantic version extracted from the tag name (`v1.0.0`),
* and populates the state to show to the user.
*/
private suspend fun fetchInfo() {
Log.d(BuildConfig.TAG, "Checking for updates...")
val asset = release.assets.find { it.name == "aliucord-manager-${release.tagName}.apk" }
?: return@mapNotNull null
val currentVersion = SemVer.parseOrNull(BuildConfig.VERSION_NAME)
?: throw Error("Failed to parse current app version")
version to asset
}
?.maxByOrNull { it.first }
?: return@launch
// Fetch releases from GitHub (60s local cache)
val releases = github.getManagerReleases().getOrThrow()
val currentVersion = SemVer.parseOrNull(BuildConfig.VERSION_NAME)
?: throw Error("Failed to parse app version")
// Find the latest release + APK release asset
val (version, apkUrl) = releases
.mapNotNull { release ->
val version = SemVer.parseOrNull(release.tagName)
?: return@mapNotNull null
if (currentVersion >= version)
return@launch
val asset = release.assets.find { it.name == "aliucord-manager-${release.tagName}.apk" }
?: return@mapNotNull null
targetVersion = version.toString()
targetApkUrl = asset.browserDownloadUrl
showDialog = true
version to asset.browserDownloadUrl
}
.maxByOrNull { it.first }
?: return
// Check if currently installed version is greater
if (currentVersion >= version) {
Log.d(BuildConfig.TAG, "Already updated to latest version!")
return
}
targetVersion = version.toString()
targetApkUrl = apkUrl
showDialog = true
Log.d(BuildConfig.TAG, "Found an update! $targetVersion $targetApkUrl")
}
}

View file

@ -149,6 +149,7 @@
<string name="updater_body">A new update has been released for Aliucord Manager! It may be required in order to function properly. Would you like to update?</string>
<string name="updater_title">Update to %1$s</string>
<string name="updater_fail">Failed to check for updates!</string>
<string name="downloader_err_unknown">Download failed (Unknown)</string>
<string name="downloader_err_timeout">Download failed (Timeout)</string>