refactor: Rewrite "new version checker"

This commit is contained in:
Ahmad Ansori Palembani 2024-06-15 08:50:40 +07:00
parent d7d75f1642
commit ec37c0f87d
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
3 changed files with 138 additions and 59 deletions

View file

@ -0,0 +1,102 @@
package dev.yokai.domain.base.models
data class Version(
val type: Type,
val stage: Stage,
val major: Int,
val minor: Int = 0,
val patch: Int = 0,
val hotfix: Int = 0,
val build: Int = 0
) {
operator fun compareTo(other: Version): Int {
if (type == Type.DEBUG) {
throw IllegalStateException("Checking debug version is not allowed")
}
if (type != other.type) {
throw IllegalArgumentException("Can't compare two different version type")
}
return when {
major > other.major ||
minor > other.minor ||
patch > other.patch ||
hotfix > other.hotfix ||
build > other.build ||
stage.weight > other.stage.weight
-> 1
else -> 0
}
}
override fun toString(): String {
return "$major.$minor.$patch" + (if (hotfix > 0) ".$hotfix" else "") + (if (build > 0) "-${type.prefix}$build" else "")
}
companion object {
fun parse(string: String): Version {
var type = when {
string.startsWith("r") -> Type.NIGHTLY
string.startsWith("d") -> Type.DEBUG
else -> Type.STABLE
}
val split = string.split("-")
var stage = Stage.RELEASE
val stageCandidate = split.getOrNull(1)
if (stageCandidate != null) {
when {
stageCandidate.startsWith("r", true) -> type = Type.NIGHTLY
stageCandidate.startsWith("d", true) -> type = Type.DEBUG
}
if (type == Type.STABLE)
stage = when {
stageCandidate.startsWith("b", true) -> Stage.BETA
stageCandidate.startsWith("a", true) -> Stage.ALPHA
else -> Stage.RELEASE
}
}
val cleanBuild = stageCandidate
?.replace("[^\\d.-]".toRegex(), "") // remove the prefix
?.toInt()
val candidate = split.first()
.replace("[^\\d.-]".toRegex(), "") // remove the prefix
.split(".").map { it.toInt() }
if (candidate.size == 1 && type == Type.NIGHTLY)
return Version(
type,
stage,
0,
0,
0,
0,
candidate[0],
)
return Version(
type,
stage,
candidate.getOrNull(0) ?: 0,
candidate.getOrNull(1) ?: 0,
candidate.getOrNull(2) ?: 0,
candidate.getOrNull(3) ?: 0,
cleanBuild ?: 0,
)
}
}
enum class Stage(val weight: Int) {
RELEASE(3),
BETA(2),
ALPHA(1),
}
enum class Type(val prefix: String) {
STABLE("v"),
NIGHTLY("r"),
DEBUG("d"),
}
}

View file

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.updater
import android.content.Context
import android.os.Build
import androidx.annotation.VisibleForTesting
import dev.yokai.domain.base.models.Version
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.network.GET
@ -14,8 +15,8 @@ import eu.kanade.tachiyomi.util.system.withIOContext
import kotlinx.serialization.json.Json
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.Date
import java.util.concurrent.TimeUnit
import java.util.*
import java.util.concurrent.*
class AppUpdateChecker(
private val json: Json = Injekt.get(),
@ -87,45 +88,12 @@ class AppUpdateChecker(
}
@VisibleForTesting
fun isNewVersion(versionTag: String, currentVersion: String = BuildConfig.VERSION_NAME, isNightly: Boolean = BuildConfig.NIGHTLY): Boolean {
// Removes prefixes like "r" or "v"
val newVersion = versionTag.replace("[^\\d.-]".toRegex(), "")
val oldVersion = currentVersion.replace("[^\\d.-]".toRegex(), "")
val newPreReleaseVer = newVersion.split("-")
val oldPreReleaseVer = oldVersion.split("-")
val newSemVer = newPreReleaseVer.first().split(".").map { it.toInt() }
val isNewVersionNightly = newSemVer.size == 1 || (newPreReleaseVer.size > 1 && newPreReleaseVer[1].startsWith("r"))
val oldSemVer = oldPreReleaseVer.first().split(".").map { it.toInt() }
oldSemVer.mapIndexed { index, i ->
if (!isNewVersionNightly && !isNightly && newSemVer.getOrElse(index) { i } > i) {
return true
} else if (newSemVer.getOrElse(index) { i } < i) {
return false
}
}
// For cases of extreme patch versions (new: 1.2.3.1 vs old: 1.2.3, return true)
return if (newSemVer.size > oldSemVer.size && !isNewVersionNightly && !isNightly) {
true
} else if (newSemVer.size < oldSemVer.size && !isNewVersionNightly) {
fun isNewVersion(newVersion: String, currentVersion: String = BuildConfig.VERSION_NAME): Boolean =
try {
Version.parse(newVersion) > Version.parse(currentVersion)
} catch (e: IllegalArgumentException) {
false
} else {
// If the version numbers match, check the beta versions
val newPreVersion =
newPreReleaseVer.getOrNull(if (newPreReleaseVer.size > 1) 1 else 0)?.replace("[^\\d.-]".toRegex(), "")?.toIntOrNull()
val oldPreVersion =
oldPreReleaseVer.getOrNull(1)?.replace("[^\\d.-]".toRegex(), "")?.toIntOrNull()
when {
// For prod, don't bother with betas (current: 1.2.3 vs new: 1.2.3-b1)
oldPreVersion == null && !isNightly -> false
// For betas, always use prod builds (current: 1.2.3-b1 vs new: 1.2.3)
// For nightly, don't use prod builds
newPreVersion == null -> !isNightly
// For nightly, higher beta ver is newer (current: 1.2.3-b1 vs new: 1.2.3-b2 or r2)
else -> (oldPreVersion ?: 0) < newPreVersion
}
}
}
}
val RELEASE_TAG: String by lazy {

View file

@ -4,7 +4,8 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.network.NetworkHelper
import io.mockk.mockk
import kotlinx.serialization.json.Json
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@ -22,55 +23,63 @@ class AppUpdateCheckerTest {
@Test
fun `Check new nightly version (Tachi format)`() {
assertTrue(isNewVersion("1.2.3-r2", "1.2.3-r1", true)) // tachi format
assertTrue(isNewVersion("1.2.4-r1", "1.2.3", true)) // Unlikely to happened, but we should try anyway
assertTrue(isNewVersion("1.2.3-r2", "1.2.3-r1")) // tachi format
assertFalse(isNewVersion("1.2.4-r1", "1.2.3")) // Unlikely to happened, but we should try anyway
}
@Test
fun `Check new nightly version (Yokai format)`() {
assertTrue(isNewVersion("r2", "1.2.3-r1", true)) // yokai format
assertTrue(isNewVersion("r2", "1.2.3-r1")) // yokai format
assertFalse(isNewVersion("r1", "1.2.3")) // Unlikely to happened, but we should try anyway
}
@Test
fun `Nightly shouldn't get Prod build`() {
assertFalse(isNewVersion("1.2.3", "1.2.3-r2", true))
assertFalse(isNewVersion("1.2.4", "1.2.3-r2", true))
assertFalse(isNewVersion("1.2.4", "1.2.3", true))
assertFalse(isNewVersion("1.2.4.1", "1.2.4-r2", true))
assertFalse(isNewVersion("1.2.3", "1.2.3-r2"))
assertFalse(isNewVersion("1.2.4", "1.2.3-r2"))
assertFalse(isNewVersion("1.2.4", "1.2.3-r0"))
assertFalse(isNewVersion("1.2.4.1", "1.2.4-r2"))
}
@Test
fun `Check new beta version`() {
assertFalse(isNewVersion("1.2.3-b1", "1.2.3-b2"))
assertTrue(isNewVersion("1.2.3-b3", "1.2.3-b2"))
assertTrue(isNewVersion("1.2.4-b1", "1.2.3-b1"))
}
@Test
fun `Beta should get Prod build`() {
assertTrue(isNewVersion("1.2.4", "1.2.3-r2", false))
assertTrue(isNewVersion("1.2.3", "1.2.3-r2", false))
assertTrue(isNewVersion("1.2.4", "1.2.3-b2"))
assertTrue(isNewVersion("1.2.3", "1.2.3-b2"))
}
@Test
fun `Prod should get latest Prod build`() {
assertTrue(isNewVersion("1.2.4", "1.2.3", false))
assertTrue(isNewVersion("1.2.4.1", "1.2.4", false))
assertTrue(isNewVersion("1.2.4", "1.2.3"))
assertTrue(isNewVersion("1.2.4.1", "1.2.4"))
}
@Test
fun `Prod should get latest Prod build (Check for Betas)`() {
assertTrue(isNewVersion("1.2.4-r1", "1.2.3", false))
assertTrue(isNewVersion("1.2.4-b1", "1.2.3"))
}
@Test
fun `Prod shouldn't get nightly build (Check for Betas)`() {
assertFalse(isNewVersion("r1", "1.2.3", false))
assertFalse(isNewVersion("r1", "1.2.3"))
}
@Test
fun `Latest version check`() {
assertFalse(isNewVersion("1.2.3", "1.2.3", false))
assertFalse(isNewVersion("1.2.3-r1", "1.2.3-r1", false))
assertFalse(isNewVersion("1.2.3", "1.2.3"))
assertFalse(isNewVersion("1.2.3-r1", "1.2.3-r1"))
assertFalse(isNewVersion("1.2.3-r1", "1.2.3-r1", true))
assertFalse(isNewVersion("r1", "1.2.4-r1", true))
assertFalse(isNewVersion("1.2.3-r1", "1.2.3-r1"))
assertFalse(isNewVersion("r1", "1.2.4-r1"))
}
private fun isNewVersion(newVersion: String, currentVersion: String, isNightly: Boolean): Boolean {
return appUpdateChecker.isNewVersion(newVersion, currentVersion, isNightly)
private fun isNewVersion(newVersion: String, currentVersion: String): Boolean {
return appUpdateChecker.isNewVersion(newVersion, currentVersion)
}
}