|
|
@@ -70,19 +70,15 @@ class UpdateChecker(private val context: Context) {
|
|
|
|
val now = System.currentTimeMillis()
|
|
|
|
val now = System.currentTimeMillis()
|
|
|
|
if (throttle && now - settings.lastUpdateCheckTime.value < CHECK_INTERVAL_MS) return@runCatching updateInfo.value
|
|
|
|
if (throttle && now - settings.lastUpdateCheckTime.value < CHECK_INTERVAL_MS) return@runCatching updateInfo.value
|
|
|
|
|
|
|
|
|
|
|
|
// 先查 GitHub(公开默认源),从其 release notes 动态发现额外源(如自建 Gitea),取最高版本
|
|
|
|
// 双源查询:Gitea(国内优先)+ GitHub(备用),取最高版本
|
|
|
|
// 同版本时 Gitea 优先(国内可达,GitHub 可能被墙导致下载失败)
|
|
|
|
// 同版本时 Gitea 优先(国内可达,GitHub 可能被墙导致下载失败)
|
|
|
|
val infos = mutableListOf<UpdateInfo>()
|
|
|
|
val infos = mutableListOf<UpdateInfo>()
|
|
|
|
var ghInfo: UpdateInfo? = null
|
|
|
|
var ghInfo: UpdateInfo? = null
|
|
|
|
var giteaInfo: UpdateInfo? = null
|
|
|
|
var giteaInfo: UpdateInfo? = null
|
|
|
|
val gh = runCatching { fetchRelease(GITHUB_API) }.getOrNull()
|
|
|
|
giteaInfo = runCatching { fetchRelease(GITEA_API) }.getOrNull()
|
|
|
|
if (gh != null) {
|
|
|
|
|
|
|
|
ghInfo = gh
|
|
|
|
|
|
|
|
giteaInfo = parseUpdateSource(gh.releaseNotes)?.let { runCatching { fetchRelease(it) }.getOrNull() }
|
|
|
|
|
|
|
|
// Gitea 放前面,同版本时优先用它下载(国内可达)
|
|
|
|
|
|
|
|
if (giteaInfo != null) infos.add(giteaInfo)
|
|
|
|
if (giteaInfo != null) infos.add(giteaInfo)
|
|
|
|
infos.add(gh)
|
|
|
|
ghInfo = runCatching { fetchRelease(GITHUB_API) }.getOrNull()
|
|
|
|
}
|
|
|
|
if (ghInfo != null) infos.add(ghInfo)
|
|
|
|
settings.setLastUpdateCheck(now)
|
|
|
|
settings.setLastUpdateCheck(now)
|
|
|
|
if (infos.isEmpty()) {
|
|
|
|
if (infos.isEmpty()) {
|
|
|
|
lastError.value = "所有更新源不可达"
|
|
|
|
lastError.value = "所有更新源不可达"
|
|
|
@@ -303,26 +299,10 @@ class UpdateChecker(private val context: Context) {
|
|
|
|
return false
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 从 release notes 的 HTML 注释里解析额外更新源(加扰编码,兼容 Base64/明文)。 */
|
|
|
|
|
|
|
|
private fun parseUpdateSource(body: String): String? {
|
|
|
|
|
|
|
|
val raw = Regex("""update-source:\s*(\S+)""").find(body)?.groupValues?.getOrNull(1) ?: return null
|
|
|
|
|
|
|
|
// 加扰格式:Base64(反转(XOR(明文, key)))。尝试完整解扰,失败则依次尝试 Base64/明文。
|
|
|
|
|
|
|
|
val b64 = runCatching { java.util.Base64.getDecoder().decode(raw) }.getOrNull() ?: return raw
|
|
|
|
|
|
|
|
val unscrambled = unscramble(b64)
|
|
|
|
|
|
|
|
return unscrambled ?: runCatching { String(b64, Charsets.UTF_8) }.getOrNull() ?: raw
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** 解扰:反转字节 → XOR 每字节 → UTF-8 字符串。 */
|
|
|
|
|
|
|
|
private fun unscramble(data: ByteArray): String? = runCatching {
|
|
|
|
|
|
|
|
val key = SCRAMBLE_KEY
|
|
|
|
|
|
|
|
val reversed = data.reversedArray()
|
|
|
|
|
|
|
|
String(ByteArray(reversed.size) { i -> ((reversed[i].toInt() and 0xFF) xor (key[i % key.size].toInt() and 0xFF)).toByte() }, Charsets.UTF_8)
|
|
|
|
|
|
|
|
}.getOrNull()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
companion object {
|
|
|
|
const val GITHUB_API = "https://api.github.com/repos/lsxf/a2i/releases/latest"
|
|
|
|
const val GITHUB_API = "https://api.github.com/repos/lsxf/a2i/releases/latest"
|
|
|
|
|
|
|
|
const val GITEA_API = "https://gitea.songer.eu.org/api/v1/repos/song/a2i/releases/latest"
|
|
|
|
const val CHECK_INTERVAL_MS = 24L * 60 * 60 * 1000
|
|
|
|
const val CHECK_INTERVAL_MS = 24L * 60 * 60 * 1000
|
|
|
|
private val SCRAMBLE_KEY = byteArrayOf(0x4B, 0x6F, 0x72, 0x6E) // "Korn"
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fun formatSize(bytes: Long): String {
|
|
|
|
fun formatSize(bytes: Long): String {
|
|
|
|