fix: download fallback URL when primary fails

- Gitea URL used as primary (same-version tie-break), GitHub as fallback
- download() tries base url first; if it fails, automatically retries
  the fallback URL
- version 1.9.1 -> 1.9.2 (code 30 -> 31)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 14:42:51 +08:00
parent b14ee02ef8
commit 6e29c292ec
2 changed files with 30 additions and 11 deletions
+2 -2
View File
@@ -13,8 +13,8 @@ android {
applicationId = "com.a2i.forwarder"
minSdk = 34
targetSdk = 36
versionCode = 29
versionName = "1.9.0"
versionCode = 30
versionName = "1.9.1"
}
signingConfigs {
@@ -26,6 +26,7 @@ data class UpdateInfo(
val apkSize: Long,
val releaseNotes: String,
val releasePageUrl: String,
val fallbackUrl: String? = null, // 主 URL 下载失败时尝试的备选链接
)
/**
@@ -71,9 +72,12 @@ class UpdateChecker(private val context: Context) {
// 先查 GitHub(公开默认源),从其 release notes 动态发现额外源(如自建 Gitea),取最高版本
// 同版本时 Gitea 优先(国内可达,GitHub 可能被墙导致下载失败)
val infos = mutableListOf<UpdateInfo>()
var ghInfo: UpdateInfo? = null
var giteaInfo: UpdateInfo? = null
val gh = runCatching { fetchRelease(GITHUB_API) }.getOrNull()
if (gh != null) {
val giteaInfo = parseUpdateSource(gh.releaseNotes)?.let { runCatching { fetchRelease(it) }.getOrNull() }
ghInfo = gh
giteaInfo = parseUpdateSource(gh.releaseNotes)?.let { runCatching { fetchRelease(it) }.getOrNull() }
// Gitea 放前面,同版本时优先用它下载(国内可达)
if (giteaInfo != null) infos.add(giteaInfo)
infos.add(gh)
@@ -92,7 +96,9 @@ class UpdateChecker(private val context: Context) {
updateInfo.value = null
lastNotifiedVersion = null
} else {
updateInfo.value = best
// 主 URL 下载失败时尝试另一个源的链接
val fallback = if (best == giteaInfo) ghInfo?.apkUrl else giteaInfo?.apkUrl
updateInfo.value = best.copy(fallbackUrl = fallback)
}
lastError.value = null
@@ -179,14 +185,29 @@ class UpdateChecker(private val context: Context) {
downloading.value = true
downloadProgress.value = 0
lastError.value = null
try {
val req = Request.Builder().url(info.apkUrl).get().build()
// 主 URL 失败时自动尝试备选 URL
val urls = listOf(info.apkUrl) + (info.fallbackUrl?.let { listOf(it) } ?: emptyList())
for ((idx, url) in urls.withIndex()) {
if (idx > 0) downloadProgress.value = 0 // 重试时重置进度
val file = tryDownload(url, info.latestVersion)
if (file != null) {
downloading.value = false
return@withContext file
}
}
downloading.value = false
null
}
private suspend fun tryDownload(url: String, version: String): File? {
return try {
val req = Request.Builder().url(url).get().build()
http.newCall(req).execute().use { res ->
if (!res.isSuccessful) { lastError.value = "HTTP ${res.code}"; return@use null }
val body = res.body ?: run { lastError.value = "空响应"; return@use null }
if (!res.isSuccessful) { lastError.value = "HTTP ${res.code}"; return@tryDownload null }
val body = res.body ?: run { lastError.value = "空响应"; return@tryDownload null }
val total = body.contentLength()
val dir = context.getExternalFilesDir(null) ?: context.cacheDir
val target = File(dir, "gotmsg-${info.latestVersion}.apk")
val target = File(dir, "gotmsg-${version}.apk")
target.outputStream().use { sink ->
val input = body.byteStream()
val buf = ByteArray(8 * 1024)
@@ -205,8 +226,6 @@ class UpdateChecker(private val context: Context) {
} catch (e: Exception) {
lastError.value = e.message ?: "下载失败"
null
} finally {
downloading.value = false
}
}