|
|
@@ -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
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|