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