fix: startup always checks + dual-source (Gitea+GitHub) take highest
- start(): check on every launch (no 24h throttle) so the banner shows after restart even if checked manually moments ago; plus a 24h loop while app is resident - check both Gitea and GitHub release APIs, skip whichever is unreachable, notify the higher version - version 1.7.2 -> 1.7.3 (code 15 -> 16) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
@@ -49,51 +50,71 @@ class UpdateChecker(private val context: Context) {
|
||||
fun start() {
|
||||
scope.launch {
|
||||
delay(5000) // 启动 5 秒后首检,给网络/应用初始化留时间
|
||||
check(force = false)
|
||||
doCheck(throttle = false, toggle = true) // 启动必查(无视节流,但尊重开关)
|
||||
while (isActive) {
|
||||
delay(CHECK_INTERVAL_MS) // 常驻期间每 24h 巡检一次
|
||||
doCheck(throttle = true, toggle = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun check(force: Boolean): Result<UpdateInfo?> = withContext(Dispatchers.IO) {
|
||||
/** 手动检查:无视节流和开关(用户主动)。 */
|
||||
suspend fun check(force: Boolean): Result<UpdateInfo?> = doCheck(throttle = !force, toggle = !force)
|
||||
|
||||
private suspend fun doCheck(throttle: Boolean, toggle: Boolean): Result<UpdateInfo?> = withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val settings = app.settings
|
||||
if (toggle && !settings.updateCheckEnabled.value) return@runCatching updateInfo.value
|
||||
val now = System.currentTimeMillis()
|
||||
// 自动检查跳过条件(仅 force=false 时有效)
|
||||
if (!force) {
|
||||
if (!settings.updateCheckEnabled.value) return@runCatching updateInfo.value
|
||||
if (now - settings.lastUpdateCheckTime.value < CHECK_INTERVAL_MS) return@runCatching updateInfo.value
|
||||
}
|
||||
val req = Request.Builder()
|
||||
.url(RELEASES_API)
|
||||
.header("Accept", "application/json")
|
||||
.header("User-Agent", "a2i-android")
|
||||
.get()
|
||||
.build()
|
||||
val info = http.newCall(req).execute().use { res ->
|
||||
if (!res.isSuccessful) error("HTTP ${res.code}")
|
||||
val body = res.body?.string() ?: error("空响应")
|
||||
parseRelease(body)
|
||||
if (throttle && now - settings.lastUpdateCheckTime.value < CHECK_INTERVAL_MS) return@runCatching updateInfo.value
|
||||
|
||||
// 双源查询(Gitea 国内优先 + GitHub 备用),任一不通跳过,取最高版本
|
||||
val infos = RELEASES_APIS.mapNotNull { api ->
|
||||
runCatching { fetchRelease(api) }.getOrNull()
|
||||
}
|
||||
settings.setLastUpdateCheck(now)
|
||||
if (infos.isEmpty()) {
|
||||
lastError.value = "所有更新源不可达"
|
||||
return@runCatching updateInfo.value
|
||||
}
|
||||
val current = currentVersionName()
|
||||
val available = info != null &&
|
||||
isNewer(info.latestVersion, current) &&
|
||||
info.latestVersion != settings.ignoredVersion.value
|
||||
val best = infos
|
||||
.filter { isNewer(it.latestVersion, current) && it.latestVersion != settings.ignoredVersion.value }
|
||||
.maxByOrNull { versionKey(it.latestVersion) }
|
||||
val prev = updateInfo.value
|
||||
updateInfo.value = if (available) info else {
|
||||
lastNotifiedVersion = null // 清除记录,等下一个新版
|
||||
null
|
||||
if (best == null) {
|
||||
updateInfo.value = null
|
||||
lastNotifiedVersion = null
|
||||
} else {
|
||||
updateInfo.value = best
|
||||
}
|
||||
lastError.value = null
|
||||
|
||||
// 发现新版且之前没推过通知 → 自动推送到所有已开启通道
|
||||
if (available && info != null && info.latestVersion != lastNotifiedVersion) {
|
||||
lastNotifiedVersion = info.latestVersion
|
||||
notifyNewVersion(prev, info)
|
||||
if (best != null && best.latestVersion != lastNotifiedVersion) {
|
||||
lastNotifiedVersion = best.latestVersion
|
||||
notifyNewVersion(prev, best)
|
||||
}
|
||||
updateInfo.value
|
||||
}.onFailure { lastError.value = it.message ?: "检查失败" }
|
||||
}
|
||||
|
||||
private fun fetchRelease(api: String): UpdateInfo? {
|
||||
val req = Request.Builder()
|
||||
.url(api)
|
||||
.header("Accept", "application/json")
|
||||
.header("User-Agent", "a2i-android")
|
||||
.get()
|
||||
.build()
|
||||
return http.newCall(req).execute().use { res ->
|
||||
if (!res.isSuccessful) error("HTTP ${res.code}")
|
||||
parseRelease(res.body?.string() ?: error("空响应"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun versionKey(v: String): String =
|
||||
v.split(".").joinToString(".") { (it.substringBefore("-").toIntOrNull() ?: 0).toString().padStart(4, '0') }
|
||||
|
||||
/** 发现新版后,自动通过所有已开启的推送通道通知用户 */
|
||||
private fun notifyNewVersion(prev: UpdateInfo?, info: UpdateInfo) {
|
||||
val s = app.settings
|
||||
@@ -225,7 +246,11 @@ class UpdateChecker(private val context: Context) {
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val RELEASES_API = "https://UPDATE_SOURCE_HIDDEN/api/v1/repos/song/a2i/releases/latest"
|
||||
// 双源:Gitea(国内优先)+ GitHub(备用),任一不通跳过,取最高版本
|
||||
val RELEASES_APIS = listOf(
|
||||
"https://UPDATE_SOURCE_HIDDEN/api/v1/repos/song/a2i/releases/latest",
|
||||
"https://api.github.com/repos/lsxf/a2i/releases/latest",
|
||||
)
|
||||
const val CHECK_INTERVAL_MS = 24L * 60 * 60 * 1000
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user