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:
@@ -13,8 +13,8 @@ android {
|
|||||||
applicationId = "com.a2i.forwarder"
|
applicationId = "com.a2i.forwarder"
|
||||||
minSdk = 34
|
minSdk = 34
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 29
|
versionCode = 30
|
||||||
versionName = "1.9.0"
|
versionName = "1.9.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ data class UpdateInfo(
|
|||||||
val apkSize: Long,
|
val apkSize: Long,
|
||||||
val releaseNotes: String,
|
val releaseNotes: String,
|
||||||
val releasePageUrl: String,
|
val releasePageUrl: String,
|
||||||
|
val fallbackUrl: String? = null, // 主 URL 下载失败时尝试的备选链接
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,9 +72,12 @@ class UpdateChecker(private val context: Context) {
|
|||||||
// 先查 GitHub(公开默认源),从其 release notes 动态发现额外源(如自建 Gitea),取最高版本
|
// 先查 GitHub(公开默认源),从其 release notes 动态发现额外源(如自建 Gitea),取最高版本
|
||||||
// 同版本时 Gitea 优先(国内可达,GitHub 可能被墙导致下载失败)
|
// 同版本时 Gitea 优先(国内可达,GitHub 可能被墙导致下载失败)
|
||||||
val infos = mutableListOf<UpdateInfo>()
|
val infos = mutableListOf<UpdateInfo>()
|
||||||
|
var ghInfo: UpdateInfo? = null
|
||||||
|
var giteaInfo: UpdateInfo? = null
|
||||||
val gh = runCatching { fetchRelease(GITHUB_API) }.getOrNull()
|
val gh = runCatching { fetchRelease(GITHUB_API) }.getOrNull()
|
||||||
if (gh != null) {
|
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 放前面,同版本时优先用它下载(国内可达)
|
// Gitea 放前面,同版本时优先用它下载(国内可达)
|
||||||
if (giteaInfo != null) infos.add(giteaInfo)
|
if (giteaInfo != null) infos.add(giteaInfo)
|
||||||
infos.add(gh)
|
infos.add(gh)
|
||||||
@@ -92,7 +96,9 @@ class UpdateChecker(private val context: Context) {
|
|||||||
updateInfo.value = null
|
updateInfo.value = null
|
||||||
lastNotifiedVersion = null
|
lastNotifiedVersion = null
|
||||||
} else {
|
} else {
|
||||||
updateInfo.value = best
|
// 主 URL 下载失败时尝试另一个源的链接
|
||||||
|
val fallback = if (best == giteaInfo) ghInfo?.apkUrl else giteaInfo?.apkUrl
|
||||||
|
updateInfo.value = best.copy(fallbackUrl = fallback)
|
||||||
}
|
}
|
||||||
lastError.value = null
|
lastError.value = null
|
||||||
|
|
||||||
@@ -179,14 +185,29 @@ class UpdateChecker(private val context: Context) {
|
|||||||
downloading.value = true
|
downloading.value = true
|
||||||
downloadProgress.value = 0
|
downloadProgress.value = 0
|
||||||
lastError.value = null
|
lastError.value = null
|
||||||
try {
|
// 主 URL 失败时自动尝试备选 URL
|
||||||
val req = Request.Builder().url(info.apkUrl).get().build()
|
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 ->
|
http.newCall(req).execute().use { res ->
|
||||||
if (!res.isSuccessful) { lastError.value = "HTTP ${res.code}"; return@use null }
|
if (!res.isSuccessful) { lastError.value = "HTTP ${res.code}"; return@tryDownload null }
|
||||||
val body = res.body ?: run { lastError.value = "空响应"; return@use null }
|
val body = res.body ?: run { lastError.value = "空响应"; return@tryDownload null }
|
||||||
val total = body.contentLength()
|
val total = body.contentLength()
|
||||||
val dir = context.getExternalFilesDir(null) ?: context.cacheDir
|
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 ->
|
target.outputStream().use { sink ->
|
||||||
val input = body.byteStream()
|
val input = body.byteStream()
|
||||||
val buf = ByteArray(8 * 1024)
|
val buf = ByteArray(8 * 1024)
|
||||||
@@ -205,8 +226,6 @@ class UpdateChecker(private val context: Context) {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
lastError.value = e.message ?: "下载失败"
|
lastError.value = e.message ?: "下载失败"
|
||||||
null
|
null
|
||||||
} finally {
|
|
||||||
downloading.value = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user