From 23c90238b12bdd11380eca2eb02c51c79654c070 Mon Sep 17 00:00:00 2001 From: Moustachauve <2206577+Moustachauve@users.noreply.github.com> Date: Tue, 5 May 2026 01:05:53 -0400 Subject: [PATCH 1/5] Fix version sorting by using SemVer instead of publishedDate --- .../repository/VersionDao.kt | 17 +---------- .../repository/VersionWithAssetsRepository.kt | 29 +++++++++++++++++-- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionDao.kt b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionDao.kt index 33d9fe86..0000807f 100644 --- a/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionDao.kt +++ b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionDao.kt @@ -43,25 +43,10 @@ interface VersionDao { """ SELECT * FROM version WHERE repositoryId = :repositoryId - AND isPrerelease = 0 AND tagName != '$IGNORED_TAG' - ORDER BY publishedDate DESC - LIMIT 1 """, ) - suspend fun getLatestStableVersionWithAssets(repositoryId: Long): VersionWithAssets? - - @Transaction - @Query( - """ - SELECT * FROM version - WHERE repositoryId = :repositoryId - AND tagName != '$IGNORED_TAG' - ORDER BY publishedDate DESC - LIMIT 1 - """, - ) - suspend fun getLatestBetaVersionWithAssets(repositoryId: Long): VersionWithAssets? + suspend fun getVersionsWithAssetsByRepository(repositoryId: Long): List @Transaction @Query("SELECT * FROM version WHERE repositoryId = :repositoryId AND tagName = :tagName LIMIT 1") diff --git a/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepository.kt b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepository.kt index 03cee2ad..34c96b7c 100644 --- a/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepository.kt +++ b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepository.kt @@ -77,11 +77,36 @@ class VersionWithAssetsRepository @Inject constructor( } suspend fun getLatestStableVersionWithAssets(repositoryId: Long): VersionWithAssets? = - versionDao.getLatestStableVersionWithAssets(repositoryId) + versionDao.getVersionsWithAssetsByRepository(repositoryId) + .filter { !it.version.isPrerelease } + .maxWithOrNull(SemVerComparator) suspend fun getLatestBetaVersionWithAssets(repositoryId: Long): VersionWithAssets? = - versionDao.getLatestBetaVersionWithAssets(repositoryId) + versionDao.getVersionsWithAssetsByRepository(repositoryId) + .maxWithOrNull(SemVerComparator) suspend fun getVersionByTag(repositoryId: Long, tagName: String): VersionWithAssets? = versionDao.getVersionByTagName(repositoryId, tagName) + + companion object { + val SemVerComparator = Comparator { v1, v2 -> + val semver1 = runCatching { + com.vdurmont.semver4j.Semver(v1.version.tagName, com.vdurmont.semver4j.Semver.SemverType.LOOSE) + }.getOrNull() + + val semver2 = runCatching { + com.vdurmont.semver4j.Semver(v2.version.tagName, com.vdurmont.semver4j.Semver.SemverType.LOOSE) + }.getOrNull() + + if (semver1 != null && semver2 != null) { + semver1.compareTo(semver2) + } else if (semver1 != null) { + 1 + } else if (semver2 != null) { + -1 + } else { + v1.version.publishedDate.compareTo(v2.version.publishedDate) + } + } + } } From a216ac0aa071992480c47f166088cd5ae0e6c146 Mon Sep 17 00:00:00 2001 From: Moustachauve <2206577+Moustachauve@users.noreply.github.com> Date: Tue, 5 May 2026 01:09:13 -0400 Subject: [PATCH 2/5] Add unit tests for SemVer comparator --- .../VersionWithAssetsRepositoryTest.kt | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 app/src/test/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepositoryTest.kt diff --git a/app/src/test/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepositoryTest.kt b/app/src/test/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepositoryTest.kt new file mode 100644 index 00000000..c8691510 --- /dev/null +++ b/app/src/test/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepositoryTest.kt @@ -0,0 +1,46 @@ +package ca.cgagnier.wlednativeandroid.repository + +import ca.cgagnier.wlednativeandroid.model.Version +import ca.cgagnier.wlednativeandroid.model.VersionWithAssets +import org.junit.Assert.assertEquals +import org.junit.Test + +class VersionWithAssetsRepositoryTest { + + @Test + fun semVerComparator_sortsCorrectly() { + val versions = listOf( + createVersion("0.14.0", "2023-01-01T00:00:00Z"), + createVersion("0.15.0", "2023-06-01T00:00:00Z"), + createVersion("0.15.5", "2023-07-01T00:00:00Z"), + createVersion("16.0.0", "2022-01-01T00:00:00Z"), // Older date, newer semver + createVersion("invalid-tag", "2024-01-01T00:00:00Z"), // Fallback to date + createVersion("invalid-old", "2023-12-01T00:00:00Z"), // Fallback to date + ).shuffled() + + val sorted = versions.sortedWith(VersionWithAssetsRepository.SemVerComparator) + + // Invalid semver tags are sorted by date and placed before valid semver tags + assertEquals("invalid-old", sorted[0].version.tagName) + assertEquals("invalid-tag", sorted[1].version.tagName) + // Valid semver tags are sorted by semver + assertEquals("0.14.0", sorted[2].version.tagName) + assertEquals("0.15.0", sorted[3].version.tagName) + assertEquals("0.15.5", sorted[4].version.tagName) + assertEquals("16.0.0", sorted[5].version.tagName) + } + + private fun createVersion(tagName: String, publishedDate: String): VersionWithAssets = VersionWithAssets( + version = Version( + id = 0, + repositoryId = 0, + tagName = tagName, + name = tagName, + description = "", + isPrerelease = false, + publishedDate = publishedDate, + htmlUrl = "", + ), + assets = emptyList(), + ) +} From 84d6140216fa6dcc175b42b9a678407cfdf06c20 Mon Sep 17 00:00:00 2001 From: Moustachauve <2206577+Moustachauve@users.noreply.github.com> Date: Tue, 5 May 2026 01:21:47 -0400 Subject: [PATCH 3/5] Address PR feedback: optimize version fetching and SemVer parsing --- .../repository/VersionDao.kt | 17 ------ .../repository/VersionWithAssetsRepository.kt | 59 +++++++++++-------- .../VersionWithAssetsRepositoryTest.kt | 43 ++++++++------ 3 files changed, 59 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionDao.kt b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionDao.kt index 0000807f..473478d5 100644 --- a/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionDao.kt +++ b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionDao.kt @@ -11,13 +11,6 @@ import ca.cgagnier.wlednativeandroid.model.Version import ca.cgagnier.wlednativeandroid.model.VersionWithAssets import kotlinx.coroutines.flow.Flow -/** - * nightly tag is not supported at the moment. Exclude it from results. - * TODO: Add support for nightly tags. This will need special handling since the tag itself never - * changes. Probably need a new Branch option for it too. - */ -private const val IGNORED_TAG = "nightly" - @Dao interface VersionDao { @Insert(onConflict = OnConflictStrategy.REPLACE) @@ -38,16 +31,6 @@ interface VersionDao { @Query("SELECT * FROM version WHERE repositoryId = :repositoryId") suspend fun getVersionsByRepository(repositoryId: Long): List - @Transaction - @Query( - """ - SELECT * FROM version - WHERE repositoryId = :repositoryId - AND tagName != '$IGNORED_TAG' - """, - ) - suspend fun getVersionsWithAssetsByRepository(repositoryId: Long): List - @Transaction @Query("SELECT * FROM version WHERE repositoryId = :repositoryId AND tagName = :tagName LIMIT 1") suspend fun getVersionByTagName(repositoryId: Long, tagName: String): VersionWithAssets? diff --git a/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepository.kt b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepository.kt index 34c96b7c..184740a3 100644 --- a/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepository.kt +++ b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepository.kt @@ -8,6 +8,8 @@ import ca.cgagnier.wlednativeandroid.model.Version import ca.cgagnier.wlednativeandroid.model.VersionWithAssets import javax.inject.Inject +private const val IGNORED_TAG = "nightly" + class VersionWithAssetsRepository @Inject constructor( private val database: DevicesDatabase, private val repositoryDao: RepositoryDao, @@ -76,37 +78,46 @@ class VersionWithAssetsRepository @Inject constructor( } } - suspend fun getLatestStableVersionWithAssets(repositoryId: Long): VersionWithAssets? = - versionDao.getVersionsWithAssetsByRepository(repositoryId) - .filter { !it.version.isPrerelease } - .maxWithOrNull(SemVerComparator) + suspend fun getLatestStableVersionWithAssets(repositoryId: Long): VersionWithAssets? { + val latestVersion = getLatestVersion( + versionDao.getVersionsByRepository(repositoryId).filter { !it.isPrerelease && it.tagName != IGNORED_TAG }, + ) + return latestVersion?.let { versionDao.getVersionByTagName(repositoryId, it.tagName) } + } - suspend fun getLatestBetaVersionWithAssets(repositoryId: Long): VersionWithAssets? = - versionDao.getVersionsWithAssetsByRepository(repositoryId) - .maxWithOrNull(SemVerComparator) + suspend fun getLatestBetaVersionWithAssets(repositoryId: Long): VersionWithAssets? { + val latestVersion = getLatestVersion( + versionDao.getVersionsByRepository(repositoryId).filter { it.tagName != IGNORED_TAG }, + ) + return latestVersion?.let { versionDao.getVersionByTagName(repositoryId, it.tagName) } + } suspend fun getVersionByTag(repositoryId: Long, tagName: String): VersionWithAssets? = versionDao.getVersionByTagName(repositoryId, tagName) companion object { - val SemVerComparator = Comparator { v1, v2 -> - val semver1 = runCatching { - com.vdurmont.semver4j.Semver(v1.version.tagName, com.vdurmont.semver4j.Semver.SemverType.LOOSE) - }.getOrNull() + val SemVerComparator = + Comparator> { v1, v2 -> + val semver1 = v1.second + val semver2 = v2.second + + if (semver1 != null && semver2 != null) { + semver1.compareTo(semver2) + } else if (semver1 != null) { + 1 + } else if (semver2 != null) { + -1 + } else { + v1.first.publishedDate.compareTo(v2.first.publishedDate) + } + } - val semver2 = runCatching { - com.vdurmont.semver4j.Semver(v2.version.tagName, com.vdurmont.semver4j.Semver.SemverType.LOOSE) + fun getLatestVersion( + versions: List, + ): ca.cgagnier.wlednativeandroid.model.Version? = versions.map { + it to runCatching { + com.vdurmont.semver4j.Semver(it.tagName, com.vdurmont.semver4j.Semver.SemverType.LOOSE) }.getOrNull() - - if (semver1 != null && semver2 != null) { - semver1.compareTo(semver2) - } else if (semver1 != null) { - 1 - } else if (semver2 != null) { - -1 - } else { - v1.version.publishedDate.compareTo(v2.version.publishedDate) - } - } + }.maxWithOrNull(SemVerComparator)?.first } } diff --git a/app/src/test/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepositoryTest.kt b/app/src/test/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepositoryTest.kt index c8691510..9cab7332 100644 --- a/app/src/test/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepositoryTest.kt +++ b/app/src/test/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepositoryTest.kt @@ -18,29 +18,34 @@ class VersionWithAssetsRepositoryTest { createVersion("invalid-old", "2023-12-01T00:00:00Z"), // Fallback to date ).shuffled() - val sorted = versions.sortedWith(VersionWithAssetsRepository.SemVerComparator) + val parsedVersions = versions.map { + it to runCatching { + com.vdurmont.semver4j.Semver(it.tagName, com.vdurmont.semver4j.Semver.SemverType.LOOSE) + }.getOrNull() + } + val sorted = parsedVersions.sortedWith(VersionWithAssetsRepository.SemVerComparator).map { it.first } // Invalid semver tags are sorted by date and placed before valid semver tags - assertEquals("invalid-old", sorted[0].version.tagName) - assertEquals("invalid-tag", sorted[1].version.tagName) + assertEquals("invalid-old", sorted[0].tagName) + assertEquals("invalid-tag", sorted[1].tagName) // Valid semver tags are sorted by semver - assertEquals("0.14.0", sorted[2].version.tagName) - assertEquals("0.15.0", sorted[3].version.tagName) - assertEquals("0.15.5", sorted[4].version.tagName) - assertEquals("16.0.0", sorted[5].version.tagName) + assertEquals("0.14.0", sorted[2].tagName) + assertEquals("0.15.0", sorted[3].tagName) + assertEquals("0.15.5", sorted[4].tagName) + assertEquals("16.0.0", sorted[5].tagName) + + val latest = VersionWithAssetsRepository.getLatestVersion(versions) + assertEquals("16.0.0", latest?.tagName) } - private fun createVersion(tagName: String, publishedDate: String): VersionWithAssets = VersionWithAssets( - version = Version( - id = 0, - repositoryId = 0, - tagName = tagName, - name = tagName, - description = "", - isPrerelease = false, - publishedDate = publishedDate, - htmlUrl = "", - ), - assets = emptyList(), + private fun createVersion(tagName: String, publishedDate: String): Version = Version( + id = 0, + repositoryId = 0, + tagName = tagName, + name = tagName, + description = "", + isPrerelease = false, + publishedDate = publishedDate, + htmlUrl = "", ) } From ebb9ed8c02707f4f2d7215d985aac1c4f18ea94c Mon Sep 17 00:00:00 2001 From: Moustachauve <2206577+Moustachauve@users.noreply.github.com> Date: Tue, 5 May 2026 01:23:18 -0400 Subject: [PATCH 4/5] Re-add TODO and comment for nightly tag support --- .../repository/VersionWithAssetsRepository.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepository.kt b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepository.kt index 184740a3..f9f14440 100644 --- a/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepository.kt +++ b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepository.kt @@ -8,6 +8,11 @@ import ca.cgagnier.wlednativeandroid.model.Version import ca.cgagnier.wlednativeandroid.model.VersionWithAssets import javax.inject.Inject +/** + * nightly tag is not supported at the moment. Exclude it from results. + * TODO: Add support for nightly tags. This will need special handling since the tag itself never + * changes. Probably need a new Branch option for it too. + */ private const val IGNORED_TAG = "nightly" class VersionWithAssetsRepository @Inject constructor( From e14c55eacfd7312d8c52ab6282f0bfb84821c7c6 Mon Sep 17 00:00:00 2001 From: Moustachauve <2206577+Moustachauve@users.noreply.github.com> Date: Tue, 5 May 2026 21:08:41 -0400 Subject: [PATCH 5/5] Refactor VersionWithAssetsRepository: improve readability and idiomatic code --- .../repository/VersionWithAssetsRepository.kt | 26 ++++++++----------- .../VersionWithAssetsRepositoryTest.kt | 5 ++-- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepository.kt b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepository.kt index f9f14440..4d1a1c30 100644 --- a/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepository.kt +++ b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepository.kt @@ -6,6 +6,7 @@ import ca.cgagnier.wlednativeandroid.model.Asset import ca.cgagnier.wlednativeandroid.model.Repository import ca.cgagnier.wlednativeandroid.model.Version import ca.cgagnier.wlednativeandroid.model.VersionWithAssets +import com.vdurmont.semver4j.Semver import javax.inject.Inject /** @@ -101,28 +102,23 @@ class VersionWithAssetsRepository @Inject constructor( versionDao.getVersionByTagName(repositoryId, tagName) companion object { - val SemVerComparator = - Comparator> { v1, v2 -> + val semVerComparator = + Comparator> { v1, v2 -> val semver1 = v1.second val semver2 = v2.second - if (semver1 != null && semver2 != null) { - semver1.compareTo(semver2) - } else if (semver1 != null) { - 1 - } else if (semver2 != null) { - -1 - } else { - v1.first.publishedDate.compareTo(v2.first.publishedDate) + when { + semver1 != null && semver2 != null -> semver1.compareTo(semver2) + semver1 != null -> 1 + semver2 != null -> -1 + else -> v1.first.publishedDate.compareTo(v2.first.publishedDate) } } - fun getLatestVersion( - versions: List, - ): ca.cgagnier.wlednativeandroid.model.Version? = versions.map { + fun getLatestVersion(versions: List): Version? = versions.map { it to runCatching { - com.vdurmont.semver4j.Semver(it.tagName, com.vdurmont.semver4j.Semver.SemverType.LOOSE) + Semver(it.tagName, Semver.SemverType.LOOSE) }.getOrNull() - }.maxWithOrNull(SemVerComparator)?.first + }.maxWithOrNull(semVerComparator)?.first } } diff --git a/app/src/test/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepositoryTest.kt b/app/src/test/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepositoryTest.kt index 9cab7332..117b9614 100644 --- a/app/src/test/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepositoryTest.kt +++ b/app/src/test/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepositoryTest.kt @@ -2,6 +2,7 @@ package ca.cgagnier.wlednativeandroid.repository import ca.cgagnier.wlednativeandroid.model.Version import ca.cgagnier.wlednativeandroid.model.VersionWithAssets +import com.vdurmont.semver4j.Semver import org.junit.Assert.assertEquals import org.junit.Test @@ -20,10 +21,10 @@ class VersionWithAssetsRepositoryTest { val parsedVersions = versions.map { it to runCatching { - com.vdurmont.semver4j.Semver(it.tagName, com.vdurmont.semver4j.Semver.SemverType.LOOSE) + Semver(it.tagName, Semver.SemverType.LOOSE) }.getOrNull() } - val sorted = parsedVersions.sortedWith(VersionWithAssetsRepository.SemVerComparator).map { it.first } + val sorted = parsedVersions.sortedWith(VersionWithAssetsRepository.semVerComparator).map { it.first } // Invalid semver tags are sorted by date and placed before valid semver tags assertEquals("invalid-old", sorted[0].tagName)