diff --git a/jadx-gui/build.gradle.kts b/jadx-gui/build.gradle.kts index 936eb7c6982..801ec65a9db 100644 --- a/jadx-gui/build.gradle.kts +++ b/jadx-gui/build.gradle.kts @@ -27,6 +27,7 @@ dependencies { implementation("org.jcommander:jcommander:2.0") implementation("ch.qos.logback:logback-classic:1.5.11") + implementation("io.github.oshai:kotlin-logging-jvm:7.0.0") implementation("com.fifesoft:rsyntaxtextarea:3.4.1") implementation(files("libs/jfontchooser-1.0.5.jar")) diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java index e7d4255ef61..38928c6834f 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java @@ -666,10 +666,10 @@ private SettingsGroup makeOtherGroup() { group.addRow(NLS.str("preferences.lineNumbersMode"), lineNumbersMode); group.addRow(NLS.str("preferences.jumpOnDoubleClick"), jumpOnDoubleClick); group.addRow(NLS.str("preferences.useAlternativeFileDialog"), useAltFileDialog); - group.addRow(NLS.str("preferences.check_for_updates"), update); group.addRow(NLS.str("preferences.cfg"), cfg); group.addRow(NLS.str("preferences.raw_cfg"), rawCfg); group.addRow(NLS.str("preferences.xposed_codegen_language"), xposedCodegenLanguage); + group.addRow(NLS.str("preferences.check_for_updates"), update); group.addRow(NLS.str("preferences.update_channel"), updateChannel); return group; } diff --git a/jadx-gui/src/main/java/jadx/gui/update/JadxUpdate.kt b/jadx-gui/src/main/java/jadx/gui/update/JadxUpdate.kt index e1dfafd1b5a..2e0fcd985c1 100644 --- a/jadx-gui/src/main/java/jadx/gui/update/JadxUpdate.kt +++ b/jadx-gui/src/main/java/jadx/gui/update/JadxUpdate.kt @@ -1,31 +1,42 @@ package jadx.gui.update import com.google.gson.Gson -import com.google.gson.JsonParser +import com.google.gson.annotations.SerializedName +import io.github.oshai.kotlinlogging.KotlinLogging import jadx.api.JadxDecompiler import jadx.core.Jadx import jadx.gui.settings.JadxUpdateChannel -import jadx.gui.update.data.Artifact -import jadx.gui.update.data.Release import org.jetbrains.kotlin.konan.file.use -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import java.io.InputStream import java.io.InputStreamReader import java.net.HttpURLConnection -import java.net.URL -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter -import java.util.Date +import java.net.URI +import kotlin.reflect.KClass + +data class Release(val name: String) + +data class ArtifactList(val artifacts: List) + +data class Artifact( + val name: String, + @SerializedName("workflow_run") val workflowRun: WorkflowRun, +) + +data class WorkflowRun( + @SerializedName("head_branch") val branch: String, +) + +interface IUpdateCallback { + fun onUpdate(r: Release) +} object JadxUpdate { - private val LOG: Logger = LoggerFactory.getLogger(JadxUpdate::class.java) + private val log = KotlinLogging.logger {} const val JADX_ARTIFACTS_URL = "https://nightly.link/skylot/jadx/workflows/build-artifacts/master" const val JADX_RELEASES_URL = "https://github.com/skylot/jadx/releases" private const val GITHUB_API_URL = "https://api.github.com/repos/skylot/jadx" - private const val GITHUB_ARTIFACTS_URL = "$GITHUB_API_URL/actions/artifacts" + private const val GITHUB_LATEST_ARTIFACTS_URL = "$GITHUB_API_URL/actions/artifacts?per_page=5&page=1" private const val GITHUB_LATEST_RELEASE_URL = "$GITHUB_API_URL/releases/latest" @JvmStatic @@ -37,7 +48,7 @@ object JadxUpdate { callback.onUpdate(release) } } catch (e: Exception) { - LOG.debug("Jadx update error", e) + log.warn(e) { "Jadx update error" } } }.apply { name = "Jadx update thread" @@ -48,12 +59,12 @@ object JadxUpdate { private fun checkForNewRelease(updateChannel: JadxUpdateChannel): Release? { if (Jadx.isDevVersion()) { - LOG.debug("Ignore check for update: development version") + log.debug { "Ignore check for update: development version" } return null } - - LOG.info("Checking for updates... Update channel: {}, current version: {}", updateChannel, JadxDecompiler.getVersion()) - + log.info { + "Checking for updates... Update channel: $updateChannel, current version: ${JadxDecompiler.getVersion()}" + } return when (updateChannel) { JadxUpdateChannel.STABLE -> checkForNewStableRelease() JadxUpdateChannel.UNSTABLE -> checkForNewUnstableRelease() @@ -61,76 +72,40 @@ object JadxUpdate { } private fun checkForNewStableRelease(): Release? { - val latestRelease = get(GITHUB_LATEST_RELEASE_URL)?.let { inputStream -> - InputStreamReader(inputStream).use { - Gson().fromJson(it, Release::class.java) - } - } ?: return null - val currentVersion = JadxDecompiler.getVersion() - - if (currentVersion.equals(latestRelease.name, ignoreCase = true)) return null + if (currentVersion.startsWith("r")) { + // current version is 'unstable', but update channel set to 'stable' + log.info { "Skip update check: can't compare unstable and stable versions" } + return null + } + val latestRelease = getAndParse(GITHUB_LATEST_RELEASE_URL, Release::class) ?: return null if (VersionComparator.checkAndCompare(currentVersion, latestRelease.name) >= 0) return null - - LOG.info("Found new jadx version: {}", latestRelease) - + log.info { "Found new jadx version: ${latestRelease.name}" } return latestRelease } private fun checkForNewUnstableRelease(): Release? { - val artifacts = getArtifacts() ?: return null - - val currentVersion = JadxDecompiler.getVersion() - val currentArtifactName = "jadx-$currentVersion" - - var newestArtifact: Artifact? = null - var currentArtifact: Artifact? = null - - for (artifact in artifacts) { - if (newestArtifact == null && artifact.name.startsWith("jadx-") && !artifact.name.startsWith("jadx-gui-")) { - newestArtifact = artifact - } - if (currentArtifact == null && artifact.name == currentArtifactName) { - currentArtifact = artifact - } - if (newestArtifact != null && currentArtifact != null) break - } - - LOG.debug("Current artifact: {}, newest artifact: {}", currentArtifact, newestArtifact) - - return if (currentArtifact != null && newestArtifact != null && newestArtifact.createdAt > currentArtifact.createdAt) { - newestArtifact.let { Release().apply { name = it.name } } - } else { - null - } + val artifacts = getAndParse(GITHUB_LATEST_ARTIFACTS_URL, ArtifactList::class) + ?.artifacts + ?.filter { it.workflowRun.branch == "master" } + ?: return null + if (artifacts.isEmpty()) return null + + val latestVersion = artifacts[0].name.removePrefix("jadx-gui-").removePrefix("jadx-").substringBefore('-') + if (VersionComparator.checkAndCompare(JadxDecompiler.getVersion(), latestVersion) >= 0) return null + log.info { "Found new unstable version: $latestVersion" } + return Release(latestVersion) } - private fun getArtifacts(): List? { - return get(GITHUB_ARTIFACTS_URL)?.let { inputStream -> - InputStreamReader(inputStream).use { reader -> - val response = JsonParser.parseReader(reader).asJsonObject - - val count = response.get("total_count").asInt - LOG.debug("Fetched $count artifacts...") - - response.getAsJsonArray("artifacts").map { - val obj = it.asJsonObject - val name = obj.get("name").asString - val sizeInBytes = obj.get("size_in_bytes").asLong - val createdAt = obj.get("created_at").asString - val parsedCreatedAt = ZonedDateTime.parse(createdAt, DateTimeFormatter.ISO_ZONED_DATE_TIME) - Artifact(name, sizeInBytes, Date.from(parsedCreatedAt.toInstant())) - } + private fun getAndParse(url: String, klass: KClass): T? { + val con = URI(url).toURL().openConnection() as? HttpURLConnection + if (con == null || con.responseCode != 200) { + return null + } + return con.inputStream.use { stream -> + InputStreamReader(stream).use { reader -> + Gson().fromJson(reader, klass.java) } } } - - private fun get(url: String): InputStream? { - val con = URL(url).openConnection() as HttpURLConnection - return if (con.responseCode == 200) con.inputStream else null - } - - interface IUpdateCallback { - fun onUpdate(r: Release) - } } diff --git a/jadx-gui/src/main/java/jadx/gui/update/VersionComparator.java b/jadx-gui/src/main/java/jadx/gui/update/VersionComparator.java index fa8eebdb4ff..c0f886ea26b 100644 --- a/jadx-gui/src/main/java/jadx/gui/update/VersionComparator.java +++ b/jadx-gui/src/main/java/jadx/gui/update/VersionComparator.java @@ -14,10 +14,23 @@ private static String clean(String str) { return ""; } String result = str.trim().toLowerCase(); + if (result.startsWith("jadx-gui-")) { + result = result.substring(9); + } + if (result.startsWith("jadx-")) { + result = result.substring(5); + } if (result.charAt(0) == 'v') { result = result.substring(1); } - // treat package version as part of version + if (result.charAt(0) == 'r') { + result = result.substring(1); + int dot = result.indexOf('.'); + if (dot != -1) { + result = result.substring(0, dot); + } + } + // treat a package version as part of version result = result.replace('-', '.'); return result; } @@ -50,8 +63,7 @@ private static int compare(String str1, String str2) { private static boolean isZeroTail(String[] arr, int pos) { for (int i = pos; i < arr.length; i++) { - String s = arr[i]; - if (Integer.valueOf(s) != 0) { + if (Integer.parseInt(arr[i]) != 0) { return false; } } diff --git a/jadx-gui/src/main/java/jadx/gui/update/data/Artifact.kt b/jadx-gui/src/main/java/jadx/gui/update/data/Artifact.kt deleted file mode 100644 index 57bac8d0de9..00000000000 --- a/jadx-gui/src/main/java/jadx/gui/update/data/Artifact.kt +++ /dev/null @@ -1,9 +0,0 @@ -package jadx.gui.update.data - -import java.util.Date - -data class Artifact( - val name: String, - val sizeInBytes: Long, - val createdAt: Date, -) diff --git a/jadx-gui/src/main/java/jadx/gui/update/data/Asset.java b/jadx-gui/src/main/java/jadx/gui/update/data/Asset.java deleted file mode 100644 index 03d76751adc..00000000000 --- a/jadx-gui/src/main/java/jadx/gui/update/data/Asset.java +++ /dev/null @@ -1,75 +0,0 @@ -package jadx.gui.update.data; - -import com.google.gson.annotations.SerializedName; - -public class Asset { - private int id; - private String name; - private long size; - - @SerializedName("download_count") - private int downloadCount; - - @SerializedName("browser_download_url") - private String downloadUrl; - - @SerializedName("created_at") - private String createdAt; - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public long getSize() { - return size; - } - - public void setSize(long size) { - this.size = size; - } - - public int getDownloadCount() { - return downloadCount; - } - - public void setDownloadCount(int downloadCount) { - this.downloadCount = downloadCount; - } - - public String getDownloadUrl() { - return downloadUrl; - } - - public void setDownloadUrl(String downloadUrl) { - this.downloadUrl = downloadUrl; - } - - public String getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(String createdAt) { - this.createdAt = createdAt; - } - - @Override - public String toString() { - return name - + ", size: " + String.format("%.2fMB", size / 1024. / 1024.) - + ", downloads count: " + downloadCount - + ", url: " + downloadUrl - + ", date: " + createdAt; - } -} diff --git a/jadx-gui/src/main/java/jadx/gui/update/data/Release.java b/jadx-gui/src/main/java/jadx/gui/update/data/Release.java deleted file mode 100644 index 81b6466147c..00000000000 --- a/jadx-gui/src/main/java/jadx/gui/update/data/Release.java +++ /dev/null @@ -1,44 +0,0 @@ -package jadx.gui.update.data; - -import java.util.List; - -public class Release { - private int id; - private String name; - private List assets; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public List getAssets() { - return assets; - } - - public void setAssets(List assets) { - this.assets = assets; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(name); - for (Asset asset : getAssets()) { - sb.append("\n "); - sb.append(asset); - } - return sb.toString(); - } -} diff --git a/jadx-gui/src/test/java/jadx/gui/update/VersionComparatorTest.java b/jadx-gui/src/test/java/jadx/gui/update/VersionComparatorTest.java index ee59730926a..df07ae68eae 100644 --- a/jadx-gui/src/test/java/jadx/gui/update/VersionComparatorTest.java +++ b/jadx-gui/src/test/java/jadx/gui/update/VersionComparatorTest.java @@ -28,6 +28,11 @@ public void testCompare() { checkCompare("1.3.3.1-1", "1.3.3", 1); } + @Test + public void testCompareUnstable() { + checkCompare("r2190.ce527ed", "jadx-r2299.742d30d", -1); + } + private static void checkCompare(String a, String b, int result) { assertThat(VersionComparator.checkAndCompare(a, b)) .as("Compare %s and %s expect %d", a, b, result)