From ec37c0f87d190f862b401d6905ee9d29e5b8a3d8 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sat, 15 Jun 2024 08:50:40 +0700 Subject: [PATCH] refactor: Rewrite "new version checker" --- .../dev/yokai/domain/base/models/Version.kt | 102 ++++++++++++++++++ .../data/updater/AppUpdateChecker.kt | 46 ++------ .../data/updater/AppUpdateCheckerTest.kt | 49 +++++---- 3 files changed, 138 insertions(+), 59 deletions(-) create mode 100644 app/src/main/java/dev/yokai/domain/base/models/Version.kt diff --git a/app/src/main/java/dev/yokai/domain/base/models/Version.kt b/app/src/main/java/dev/yokai/domain/base/models/Version.kt new file mode 100644 index 0000000000..fb931aa3ff --- /dev/null +++ b/app/src/main/java/dev/yokai/domain/base/models/Version.kt @@ -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"), + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt index 5978836393..89a7f4552e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt @@ -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 { diff --git a/app/src/test/java/eu/kanade/tachiyomi/data/updater/AppUpdateCheckerTest.kt b/app/src/test/java/eu/kanade/tachiyomi/data/updater/AppUpdateCheckerTest.kt index 87209b5fff..382f35e325 100644 --- a/app/src/test/java/eu/kanade/tachiyomi/data/updater/AppUpdateCheckerTest.kt +++ b/app/src/test/java/eu/kanade/tachiyomi/data/updater/AppUpdateCheckerTest.kt @@ -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) } }