mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-07-28 21:53:38 +00:00
Improve update check code
This commit is contained in:
parent
76962f965e
commit
adbea7e313
@ -11,15 +11,9 @@ import java.io.File
|
||||
|
||||
class ManagerInstallDialog : MarkDownDialog() {
|
||||
|
||||
private val svc get() = ServiceLocator.networkService
|
||||
|
||||
override suspend fun getMarkdownText(): String {
|
||||
val str = Info.remote.magisk.note
|
||||
val text = if (str.startsWith("http", true)) svc.fetchString(str) else str
|
||||
val text = Info.remote.magisk.note
|
||||
// Cache the changelog
|
||||
AppContext.cacheDir.listFiles { _, name -> name.endsWith(".md") }.orEmpty().forEach {
|
||||
it.delete()
|
||||
}
|
||||
File(AppContext.cacheDir, "${Info.remote.magisk.versionCode}.md").writeText(text)
|
||||
return text
|
||||
}
|
||||
|
@ -71,14 +71,11 @@ class InstallViewModel(svc: NetworkService, markwon: Markwon) : BaseViewModel()
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val file = File(AppContext.cacheDir, "${BuildConfig.APP_VERSION_CODE}.md")
|
||||
val note = Info.remote.magisk.note
|
||||
val text = when {
|
||||
file.exists() -> file.readText()
|
||||
Const.APP_IS_CANARY && note.isEmpty() -> ""
|
||||
Const.APP_IS_CANARY && !note.startsWith("http", true) -> note
|
||||
else -> {
|
||||
val url = if (Const.APP_IS_CANARY) note else Const.Url.CHANGELOG_URL
|
||||
val str = svc.fetchString(url)
|
||||
val str = if (Const.APP_IS_CANARY) Info.remote.magisk.note
|
||||
else svc.fetchString(Const.Url.CHANGELOG_URL)
|
||||
file.writeText(str)
|
||||
str
|
||||
}
|
||||
@ -103,13 +100,15 @@ class InstallViewModel(svc: NetworkService, markwon: Markwon) : BaseViewModel()
|
||||
}
|
||||
|
||||
override fun onSaveState(state: Bundle) {
|
||||
state.putParcelable(INSTALL_STATE_KEY, InstallState(
|
||||
methodId,
|
||||
step,
|
||||
Config.keepVerity,
|
||||
Config.keepEnc,
|
||||
Config.recovery
|
||||
))
|
||||
state.putParcelable(
|
||||
INSTALL_STATE_KEY, InstallState(
|
||||
methodId,
|
||||
step,
|
||||
Config.keepVerity,
|
||||
Config.keepEnc,
|
||||
Config.recovery
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onRestoreState(state: Bundle) {
|
||||
@ -127,6 +126,7 @@ class InstallViewModel(svc: NetworkService, markwon: Markwon) : BaseViewModel()
|
||||
override fun onActivityLaunch() {
|
||||
AppContext.toast(CoreR.string.patch_file_msg, Toast.LENGTH_LONG)
|
||||
}
|
||||
|
||||
override fun onActivityResult(result: Uri) {
|
||||
uri.value = result
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package com.topjohnwu.magisk.core
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Process
|
||||
import com.topjohnwu.magisk.core.BuildConfig.APP_VERSION_CODE
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
object Const {
|
||||
@ -20,7 +21,7 @@ object Const {
|
||||
|
||||
// Misc
|
||||
val USER_ID = Process.myUid() / 100000
|
||||
val APP_IS_CANARY get() = Version.isCanary(BuildConfig.APP_VERSION_CODE)
|
||||
val APP_IS_CANARY get() = Version.isCanary(APP_VERSION_CODE)
|
||||
|
||||
object Version {
|
||||
const val MIN_VERSION = "v22.0"
|
||||
@ -43,12 +44,11 @@ object Const {
|
||||
const val PATREON_URL = "https://www.patreon.com/topjohnwu"
|
||||
const val SOURCE_CODE_URL = "https://github.com/topjohnwu/Magisk"
|
||||
|
||||
const val CHANGELOG_URL = "https://topjohnwu.github.io/Magisk/releases/${BuildConfig.APP_VERSION_CODE}.md"
|
||||
const val CHANGELOG_URL = "https://topjohnwu.github.io/Magisk/releases/${APP_VERSION_CODE}.md"
|
||||
|
||||
const val GITHUB_RAW_URL = "https://raw.githubusercontent.com/"
|
||||
const val GITHUB_API_URL = "https://api.github.com/"
|
||||
const val GITHUB_PAGE_URL = "https://topjohnwu.github.io/magisk-files/"
|
||||
const val JS_DELIVR_URL = "https://cdn.jsdelivr.net/gh/"
|
||||
const val INVALID_URL = "https://example.com/"
|
||||
}
|
||||
|
||||
object Key {
|
||||
|
@ -4,6 +4,7 @@ import com.topjohnwu.magisk.core.model.ModuleJson
|
||||
import com.topjohnwu.magisk.core.model.Release
|
||||
import com.topjohnwu.magisk.core.model.UpdateInfo
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Headers
|
||||
import retrofit2.http.Path
|
||||
@ -11,7 +12,7 @@ import retrofit2.http.Query
|
||||
import retrofit2.http.Streaming
|
||||
import retrofit2.http.Url
|
||||
|
||||
interface RawServices {
|
||||
interface RawUrl {
|
||||
|
||||
@GET
|
||||
@Streaming
|
||||
@ -24,20 +25,19 @@ interface RawServices {
|
||||
suspend fun fetchModuleJson(@Url url: String): ModuleJson
|
||||
|
||||
@GET
|
||||
suspend fun fetchUpdateJSON(@Url url: String): UpdateInfo
|
||||
|
||||
suspend fun fetchUpdateJson(@Url url: String): UpdateInfo
|
||||
}
|
||||
|
||||
interface GithubApiServices {
|
||||
|
||||
@GET("/repos/{owner}/{repo}/releases")
|
||||
@Headers("Accept: application/vnd.github+json")
|
||||
suspend fun fetchRelease(
|
||||
suspend fun fetchReleases(
|
||||
@Path("owner") owner: String = "topjohnwu",
|
||||
@Path("repo") repo: String = "Magisk",
|
||||
@Query("per_page") per: Int = 10,
|
||||
@Query("page") page: Int = 1,
|
||||
): List<Release>
|
||||
): Response<MutableList<Release>>
|
||||
|
||||
@GET("/repos/{owner}/{repo}/releases/latest")
|
||||
@Headers("Accept: application/vnd.github+json")
|
@ -5,6 +5,7 @@ import com.squareup.moshi.Moshi
|
||||
import com.topjohnwu.magisk.ProviderInstaller
|
||||
import com.topjohnwu.magisk.core.BuildConfig
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.model.DateTimeAdapter
|
||||
import com.topjohnwu.magisk.core.utils.LocaleSetting
|
||||
import okhttp3.Cache
|
||||
import okhttp3.ConnectionSpec
|
||||
@ -77,7 +78,7 @@ fun createOkHttpClient(context: Context): OkHttpClient {
|
||||
}
|
||||
|
||||
fun createMoshiConverterFactory(): MoshiConverterFactory {
|
||||
val moshi = Moshi.Builder().build()
|
||||
val moshi = Moshi.Builder().add(DateTimeAdapter()).build()
|
||||
return MoshiConverterFactory.create(moshi)
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@ object ServiceLocator {
|
||||
val markwon by lazy { createMarkwon(AppContext) }
|
||||
val networkService by lazy {
|
||||
NetworkService(
|
||||
createApiService(retrofit, Const.Url.GITHUB_RAW_URL),
|
||||
createApiService(retrofit, Const.Url.INVALID_URL),
|
||||
createApiService(retrofit, Const.Url.GITHUB_API_URL),
|
||||
)
|
||||
}
|
||||
|
@ -1,8 +1,14 @@
|
||||
package com.topjohnwu.magisk.core.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.squareup.moshi.FromJson
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.JsonQualifier
|
||||
import com.squareup.moshi.ToJson
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class UpdateInfo(
|
||||
@ -29,14 +35,32 @@ data class ModuleJson(
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ReleaseAssets(
|
||||
val name: String,
|
||||
val browser_download_url: String,
|
||||
@Json(name = "browser_download_url") val url: String,
|
||||
)
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@JsonQualifier
|
||||
annotation class DateTime
|
||||
|
||||
class DateTimeAdapter {
|
||||
@ToJson
|
||||
fun toJson(@DateTime date: LocalDateTime): String {
|
||||
return date.toString()
|
||||
}
|
||||
|
||||
@FromJson
|
||||
@DateTime
|
||||
fun fromJson(date: String): LocalDateTime {
|
||||
return LocalDateTime.parse(date, ISO_OFFSET_DATE_TIME)
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Release(
|
||||
val tag_name: String,
|
||||
@Json(name = "tag_name") val tag: String,
|
||||
val name: String,
|
||||
val prerelease: Boolean,
|
||||
val assets: List<ReleaseAssets>,
|
||||
val body: String,
|
||||
@Json(name = "created_at") @DateTime val createdTime: LocalDateTime,
|
||||
)
|
||||
|
@ -9,16 +9,17 @@ import com.topjohnwu.magisk.core.Config.Value.DEFAULT_CHANNEL
|
||||
import com.topjohnwu.magisk.core.Config.Value.STABLE_CHANNEL
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.data.GithubApiServices
|
||||
import com.topjohnwu.magisk.core.data.RawServices
|
||||
import com.topjohnwu.magisk.core.data.RawUrl
|
||||
import com.topjohnwu.magisk.core.model.MagiskJson
|
||||
import com.topjohnwu.magisk.core.model.Release
|
||||
import com.topjohnwu.magisk.core.model.UpdateInfo
|
||||
import retrofit2.HttpException
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
class NetworkService(
|
||||
private val raw: RawServices,
|
||||
private val raw: RawUrl,
|
||||
private val api: GithubApiServices,
|
||||
) {
|
||||
suspend fun fetchUpdate() = safe {
|
||||
@ -38,40 +39,59 @@ class NetworkService(
|
||||
info
|
||||
}
|
||||
|
||||
// UpdateInfo
|
||||
private suspend fun fetchStableUpdate(rel: Release? = null): UpdateInfo {
|
||||
val release = rel ?: api.fetchLatestRelease()
|
||||
val name = release.tag_name.drop(1)
|
||||
// Keep going through all release pages until we find a match
|
||||
private suspend inline fun findRelease(predicate: (Release) -> Boolean): Release? {
|
||||
var page = 1
|
||||
while (true) {
|
||||
val response = api.fetchReleases(page = page)
|
||||
val releases = response.body() ?: throw HttpException(response)
|
||||
// Make sure it's sorted correctly
|
||||
releases.sortByDescending { it.createdTime }
|
||||
releases.find(predicate)?.let { return it }
|
||||
if (response.headers()["link"]?.contains("rel=\"next\"", ignoreCase = true) == true) {
|
||||
page += 1
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Release.asPublicInfo(): UpdateInfo {
|
||||
val version = tag.drop(1)
|
||||
val date = createdTime.format(DateTimeFormatter.ofPattern("yyyy.M.d"))
|
||||
val info = MagiskJson(
|
||||
name, (name.toFloat() * 1000).toInt(),
|
||||
release.assets[0].browser_download_url, release.body
|
||||
version = version,
|
||||
versionCode = (version.toFloat() * 1000).toInt(),
|
||||
link = assets[0].url,
|
||||
note = "## $date $name\n\n$body"
|
||||
)
|
||||
return UpdateInfo(info)
|
||||
}
|
||||
|
||||
private suspend fun fetchBetaUpdate(): UpdateInfo {
|
||||
val release = api.fetchRelease().find { it.tag_name[0] == 'v' && it.prerelease }
|
||||
return fetchStableUpdate(release)
|
||||
}
|
||||
|
||||
private suspend fun fetchCanaryUpdate(): UpdateInfo {
|
||||
val release = api.fetchRelease().find { it.tag_name.startsWith("canary-") }
|
||||
private fun Release.asCanaryInfo(assetSelector: String): UpdateInfo {
|
||||
val info = MagiskJson(
|
||||
release!!.name.substring(8, 16),
|
||||
release.tag_name.drop(7).toInt(),
|
||||
release.assets.find { it.name == "app-release.apk" }!!.browser_download_url,
|
||||
release.body
|
||||
version = name.substring(8, 16),
|
||||
versionCode = tag.drop(7).toInt(),
|
||||
link = assets.find { it.name == assetSelector }!!.url,
|
||||
note = "## $name\n\n$body"
|
||||
)
|
||||
return UpdateInfo(info)
|
||||
}
|
||||
|
||||
private suspend fun fetchDebugUpdate(): UpdateInfo {
|
||||
val release = fetchCanaryUpdate()
|
||||
val link = release.magisk.link.replace("app-release.apk", "app-debug.apk")
|
||||
return UpdateInfo(release.magisk.copy(link = link))
|
||||
}
|
||||
private suspend fun fetchStableUpdate() = api.fetchLatestRelease().asPublicInfo()
|
||||
|
||||
private suspend fun fetchCustomUpdate(url: String) = raw.fetchUpdateJSON(url)
|
||||
private suspend fun fetchBetaUpdate() = findRelease { it.tag[0] == 'v' }!!.asPublicInfo()
|
||||
|
||||
private suspend fun fetchCanary() = findRelease { it.tag.startsWith("canary-") }!!
|
||||
|
||||
private suspend fun fetchCanaryUpdate() = fetchCanary().asCanaryInfo("app-release.apk")
|
||||
|
||||
private suspend fun fetchDebugUpdate() = fetchCanary().asCanaryInfo("app-debug.apk")
|
||||
|
||||
private suspend fun fetchCustomUpdate(url: String): UpdateInfo {
|
||||
val info = raw.fetchUpdateJson(url).magisk
|
||||
return UpdateInfo(info.let { it.copy(note = raw.fetchString(it.note)) })
|
||||
}
|
||||
|
||||
private inline fun <T> safe(factory: () -> T): T? {
|
||||
return try {
|
||||
|
Loading…
x
Reference in New Issue
Block a user