From 79160b68ec77f1e0ad58ae65160015fac5ca3eb5 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 14 Jun 2022 13:26:12 -0300 Subject: [PATCH] [db] clean up RepositoryDao and add more tests --- .../java/org/fdroid/database/AppDaoTest.kt | 15 + .../org/fdroid/database/RepositoryDaoTest.kt | 269 ++++++++++++++++ .../org/fdroid/database/RepositoryTest.kt | 110 ------- .../java/org/fdroid/database/TestUtils.kt | 7 + .../org/fdroid/index/v2/IndexV2UpdaterTest.kt | 9 + .../main/java/org/fdroid/database/AppDao.kt | 5 + .../java/org/fdroid/database/Repository.kt | 3 +- .../java/org/fdroid/database/RepositoryDao.kt | 302 +++++++++++------- 8 files changed, 489 insertions(+), 231 deletions(-) create mode 100644 database/src/dbTest/java/org/fdroid/database/RepositoryDaoTest.kt delete mode 100644 database/src/dbTest/java/org/fdroid/database/RepositoryTest.kt diff --git a/database/src/dbTest/java/org/fdroid/database/AppDaoTest.kt b/database/src/dbTest/java/org/fdroid/database/AppDaoTest.kt index b33d0444f..56822bf45 100644 --- a/database/src/dbTest/java/org/fdroid/database/AppDaoTest.kt +++ b/database/src/dbTest/java/org/fdroid/database/AppDaoTest.kt @@ -127,4 +127,19 @@ internal class AppDaoTest : AppTest() { assertEquals(0, appDao.getNumberOfAppsInCategory("C")) } + @Test + fun testGetNumberOfAppsInRepository() { + val repoId = repoDao.insertOrReplace(getRandomRepo()) + assertEquals(0, appDao.getNumberOfAppsInRepository(repoId)) + + appDao.insert(repoId, packageName1, app1, locales) + assertEquals(1, appDao.getNumberOfAppsInRepository(repoId)) + + appDao.insert(repoId, packageName2, app2, locales) + assertEquals(2, appDao.getNumberOfAppsInRepository(repoId)) + + appDao.insert(repoId, packageName3, app3, locales) + assertEquals(3, appDao.getNumberOfAppsInRepository(repoId)) + } + } diff --git a/database/src/dbTest/java/org/fdroid/database/RepositoryDaoTest.kt b/database/src/dbTest/java/org/fdroid/database/RepositoryDaoTest.kt new file mode 100644 index 000000000..7bca2cc02 --- /dev/null +++ b/database/src/dbTest/java/org/fdroid/database/RepositoryDaoTest.kt @@ -0,0 +1,269 @@ +package org.fdroid.database + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.fdroid.database.TestUtils.assertRepoEquals +import org.fdroid.database.TestUtils.getOrFail +import org.fdroid.test.TestAppUtils.getRandomMetadataV2 +import org.fdroid.test.TestRepoUtils.getRandomRepo +import org.fdroid.test.TestUtils.getRandomString +import org.fdroid.test.TestUtils.orNull +import org.fdroid.test.TestVersionUtils.getRandomPackageVersionV2 +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.random.Random +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue +import kotlin.test.fail + +@RunWith(AndroidJUnit4::class) +internal class RepositoryDaoTest : DbTest() { + + @get:Rule + val instantTaskExecutorRule = InstantTaskExecutorRule() + + @Test + fun testInsertInitialRepository() { + val repo = InitialRepository( + name = getRandomString(), + address = getRandomString(), + description = getRandomString(), + certificate = getRandomString(), + version = Random.nextLong(), + enabled = Random.nextBoolean(), + weight = Random.nextInt(), + ) + val repoId = repoDao.insert(repo) + + val actualRepo = repoDao.getRepository(repoId) ?: fail() + assertEquals(repo.name, actualRepo.getName(locales)) + assertEquals(repo.address, actualRepo.address) + assertEquals(repo.description, actualRepo.getDescription(locales)) + assertEquals(repo.certificate, actualRepo.certificate) + assertEquals(repo.version, actualRepo.version) + assertEquals(repo.enabled, actualRepo.enabled) + assertEquals(repo.weight, actualRepo.weight) + assertEquals(-1, actualRepo.timestamp) + assertEquals(emptyList(), actualRepo.mirrors) + assertEquals(emptyList(), actualRepo.userMirrors) + assertEquals(emptyList(), actualRepo.disabledMirrors) + assertEquals(emptyList(), actualRepo.getMirrors()) + assertEquals(emptyList(), actualRepo.antiFeatures) + assertEquals(emptyList(), actualRepo.categories) + assertEquals(emptyList(), actualRepo.releaseChannels) + assertNull(actualRepo.formatVersion) + assertNull(actualRepo.icon) + assertNull(actualRepo.lastUpdated) + assertNull(actualRepo.webBaseUrl) + } + + @Test + fun testInsertEmptyRepo() { + // insert empty repo + val address = getRandomString() + val username = getRandomString().orNull() + val password = getRandomString().orNull() + val repoId = repoDao.insertEmptyRepo(address, username, password) + + // check that repo got inserted as expected + val actualRepo = repoDao.getRepository(repoId) ?: fail() + assertEquals(address, actualRepo.address) + assertEquals(username, actualRepo.username) + assertEquals(password, actualRepo.password) + assertEquals(-1, actualRepo.timestamp) + assertEquals(emptyList(), actualRepo.getMirrors()) + assertEquals(emptyList(), actualRepo.antiFeatures) + assertEquals(emptyList(), actualRepo.categories) + assertEquals(emptyList(), actualRepo.releaseChannels) + assertNull(actualRepo.formatVersion) + assertNull(actualRepo.icon) + assertNull(actualRepo.lastUpdated) + assertNull(actualRepo.webBaseUrl) + } + + @Test + fun insertAndDeleteTwoRepos() { + // insert first repo + val repo1 = getRandomRepo() + val repoId1 = repoDao.insertOrReplace(repo1) + + // check that first repo got added and retrieved as expected + repoDao.getRepositories().let { repos -> + assertEquals(1, repos.size) + assertRepoEquals(repo1, repos[0]) + } + val repositoryPreferences1 = repoDao.getRepositoryPreferences(repoId1) + assertEquals(repoId1, repositoryPreferences1?.repoId) + + // insert second repo + val repo2 = getRandomRepo() + val repoId2 = repoDao.insertOrReplace(repo2) + + // check that both repos got added and retrieved as expected + listOf( + repoDao.getRepositories().sortedBy { it.repoId }, + repoDao.getLiveRepositories().getOrFail().sortedBy { it.repoId }, + ).forEach { repos -> + assertEquals(2, repos.size) + assertRepoEquals(repo1, repos[0]) + assertRepoEquals(repo2, repos[1]) + } + val repositoryPreferences2 = repoDao.getRepositoryPreferences(repoId2) + assertEquals(repoId2, repositoryPreferences2?.repoId) + // second repo has one weight point more than first repo + assertEquals(repositoryPreferences1?.weight?.plus(1), repositoryPreferences2?.weight) + + // remove first repo and check that the database only returns one + repoDao.deleteRepository(repoId1) + listOf( + repoDao.getRepositories(), + repoDao.getLiveRepositories().getOrFail(), + ).forEach { repos -> + assertEquals(1, repos.size) + assertRepoEquals(repo2, repos[0]) + } + assertNull(repoDao.getRepositoryPreferences(repoId1)) + + // remove second repo and check that all associated data got removed as well + repoDao.deleteRepository(repoId2) + assertEquals(0, repoDao.getRepositories().size) + assertEquals(0, repoDao.countMirrors()) + assertEquals(0, repoDao.countAntiFeatures()) + assertEquals(0, repoDao.countCategories()) + assertEquals(0, repoDao.countReleaseChannels()) + assertNull(repoDao.getRepositoryPreferences(repoId2)) + } + + @Test + fun insertTwoReposAndClearAll() { + val repo1 = getRandomRepo() + val repo2 = getRandomRepo() + repoDao.insertOrReplace(repo1) + repoDao.insertOrReplace(repo2) + assertEquals(2, repoDao.getRepositories().size) + assertEquals(2, repoDao.getLiveRepositories().getOrFail().size) + + repoDao.clearAll() + assertEquals(0, repoDao.getRepositories().size) + assertEquals(0, repoDao.getLiveRepositories().getOrFail().size) + } + + @Test + fun testSetRepositoryEnabled() { + // repo is enabled by default + val repoId = repoDao.insertOrReplace(getRandomRepo()) + assertTrue(repoDao.getRepository(repoId)?.enabled ?: fail()) + + // disabled repo is disabled + repoDao.setRepositoryEnabled(repoId, false) + assertFalse(repoDao.getRepository(repoId)?.enabled ?: fail()) + + // enabling again works + repoDao.setRepositoryEnabled(repoId, true) + assertTrue(repoDao.getRepository(repoId)?.enabled ?: fail()) + } + + @Test + fun testUpdateUserMirrors() { + // repo is enabled by default + val repoId = repoDao.insertOrReplace(getRandomRepo()) + assertEquals(emptyList(), repoDao.getRepository(repoId)?.userMirrors) + + // add user mirrors + val userMirrors = listOf(getRandomString(), getRandomString(), getRandomString()) + repoDao.updateUserMirrors(repoId, userMirrors) + val repo = repoDao.getRepository(repoId) ?: fail() + assertEquals(userMirrors, repo.userMirrors) + + // user mirrors are part of all mirrors + val userDownloadMirrors = userMirrors.map { org.fdroid.download.Mirror(it) } + assertTrue(repo.getMirrors().containsAll(userDownloadMirrors)) + + // remove user mirrors + repoDao.updateUserMirrors(repoId, emptyList()) + assertEquals(emptyList(), repoDao.getRepository(repoId)?.userMirrors) + } + + @Test + fun testUpdateUsernameAndPassword() { + // repo has no username or password initially + val repoId = repoDao.insertOrReplace(getRandomRepo()) + repoDao.getRepository(repoId)?.let { repo -> + assertEquals(null, repo.username) + assertEquals(null, repo.password) + } ?: fail() + + // add user name and password + val username = getRandomString().orNull() + val password = getRandomString().orNull() + repoDao.updateUsernameAndPassword(repoId, username, password) + repoDao.getRepository(repoId)?.let { repo -> + assertEquals(username, repo.username) + assertEquals(password, repo.password) + } ?: fail() + } + + @Test + fun testUpdateDisabledMirrors() { + // repo has no username or password initially + val repoId = repoDao.insertOrReplace(getRandomRepo()) + repoDao.getRepository(repoId)?.let { repo -> + assertEquals(null, repo.username) + assertEquals(null, repo.password) + } ?: fail() + + // add user name and password + val username = getRandomString().orNull() + val password = getRandomString().orNull() + repoDao.updateUsernameAndPassword(repoId, username, password) + repoDao.getRepository(repoId)?.let { repo -> + assertEquals(username, repo.username) + assertEquals(password, repo.password) + } ?: fail() + } + + @Test + fun clearingRepoRemovesAllAssociatedData() { + // insert one repo with one app with one version + val repoId = repoDao.insertOrReplace(getRandomRepo()) + val repositoryPreferences = repoDao.getRepositoryPreferences(repoId) + val packageId = getRandomString() + val versionId = getRandomString() + appDao.insert(repoId, packageId, getRandomMetadataV2()) + val packageVersion = getRandomPackageVersionV2() + versionDao.insert(repoId, packageId, versionId, packageVersion, Random.nextBoolean()) + + // data is there as expected + assertEquals(1, repoDao.getRepositories().size) + assertEquals(1, appDao.getAppMetadata().size) + assertEquals(1, versionDao.getAppVersions(repoId, packageId).size) + assertTrue(versionDao.getVersionedStrings(repoId, packageId).isNotEmpty()) + + // clearing the repo removes apps and versions + repoDao.clear(repoId) + assertEquals(1, repoDao.getRepositories().size) + assertEquals(0, appDao.countApps()) + assertEquals(0, appDao.countLocalizedFiles()) + assertEquals(0, appDao.countLocalizedFileLists()) + assertEquals(0, versionDao.getAppVersions(repoId, packageId).size) + assertEquals(0, versionDao.getVersionedStrings(repoId, packageId).size) + // preferences are not touched by clearing + assertEquals(repositoryPreferences, repoDao.getRepositoryPreferences(repoId)) + } + + @Test + fun certGetsUpdated() { + val repoId = repoDao.insertOrReplace(getRandomRepo()) + assertEquals(1, repoDao.getRepositories().size) + assertEquals(null, repoDao.getRepositories()[0].certificate) + + val cert = getRandomString() + repoDao.updateRepository(repoId, cert) + + assertEquals(1, repoDao.getRepositories().size) + assertEquals(cert, repoDao.getRepositories()[0].certificate) + } +} diff --git a/database/src/dbTest/java/org/fdroid/database/RepositoryTest.kt b/database/src/dbTest/java/org/fdroid/database/RepositoryTest.kt deleted file mode 100644 index d2a3e8879..000000000 --- a/database/src/dbTest/java/org/fdroid/database/RepositoryTest.kt +++ /dev/null @@ -1,110 +0,0 @@ -package org.fdroid.database - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.fdroid.database.TestUtils.assertRepoEquals -import org.fdroid.test.TestAppUtils.getRandomMetadataV2 -import org.fdroid.test.TestRepoUtils.getRandomRepo -import org.fdroid.test.TestUtils.getRandomString -import org.fdroid.test.TestVersionUtils.getRandomPackageVersionV2 -import org.junit.Test -import org.junit.runner.RunWith -import kotlin.random.Random -import kotlin.test.assertEquals -import kotlin.test.assertNull -import kotlin.test.assertTrue - -@RunWith(AndroidJUnit4::class) -internal class RepositoryTest : DbTest() { - - @Test - fun insertAndDeleteTwoRepos() { - // insert first repo - val repo1 = getRandomRepo() - val repoId1 = repoDao.insertOrReplace(repo1) - - // check that first repo got added and retrieved as expected - var repos = repoDao.getRepositories() - assertEquals(1, repos.size) - assertRepoEquals(repo1, repos[0]) - val repositoryPreferences1 = repoDao.getRepositoryPreferences(repoId1) - assertEquals(repoId1, repositoryPreferences1?.repoId) - - // insert second repo - val repo2 = getRandomRepo() - val repoId2 = repoDao.insertOrReplace(repo2) - - // check that both repos got added and retrieved as expected - repos = repoDao.getRepositories().sortedBy { it.repoId } - assertEquals(2, repos.size) - assertRepoEquals(repo1, repos[0]) - assertRepoEquals(repo2, repos[1]) - val repositoryPreferences2 = repoDao.getRepositoryPreferences(repoId2) - assertEquals(repoId2, repositoryPreferences2?.repoId) - assertEquals(repositoryPreferences1?.weight?.plus(1), repositoryPreferences2?.weight) - - // remove first repo and check that the database only returns one - repoDao.deleteRepository(repos[0].repository.repoId) - assertEquals(1, repoDao.getRepositories().size) - - // remove second repo as well and check that all associated data got removed as well - repoDao.deleteRepository(repos[1].repository.repoId) - assertEquals(0, repoDao.getRepositories().size) - assertEquals(0, repoDao.getMirrors().size) - assertEquals(0, repoDao.getAntiFeatures().size) - assertEquals(0, repoDao.getCategories().size) - assertEquals(0, repoDao.getReleaseChannels().size) - assertNull(repoDao.getRepositoryPreferences(repoId1)) - assertNull(repoDao.getRepositoryPreferences(repoId2)) - } - - @Test - fun insertTwoReposAndClearAll() { - val repo1 = getRandomRepo() - val repo2 = getRandomRepo() - repoDao.insertOrReplace(repo1) - repoDao.insertOrReplace(repo2) - assertEquals(2, repoDao.getRepositories().size) - - repoDao.clearAll() - assertEquals(0, repoDao.getRepositories().size) - } - - @Test - fun clearingRepoRemovesAllAssociatedData() { - val repoId = repoDao.insertOrReplace(getRandomRepo()) - val repositoryPreferences = repoDao.getRepositoryPreferences(repoId) - val packageId = getRandomString() - val versionId = getRandomString() - appDao.insert(repoId, packageId, getRandomMetadataV2()) - val packageVersion = getRandomPackageVersionV2() - versionDao.insert(repoId, packageId, versionId, packageVersion, Random.nextBoolean()) - - assertEquals(1, repoDao.getRepositories().size) - assertEquals(1, appDao.getAppMetadata().size) - assertEquals(1, versionDao.getAppVersions(repoId, packageId).size) - assertTrue(versionDao.getVersionedStrings(repoId, packageId).isNotEmpty()) - - repoDao.clear(repoId) - assertEquals(1, repoDao.getRepositories().size) - assertEquals(0, appDao.countApps()) - assertEquals(0, appDao.countLocalizedFiles()) - assertEquals(0, appDao.countLocalizedFileLists()) - assertEquals(0, versionDao.getAppVersions(repoId, packageId).size) - assertEquals(0, versionDao.getVersionedStrings(repoId, packageId).size) - // preferences are not touched by clearing - assertEquals(repositoryPreferences, repoDao.getRepositoryPreferences(repoId)) - } - - @Test - fun certGetsUpdated() { - val repoId = repoDao.insertOrReplace(getRandomRepo()) - assertEquals(1, repoDao.getRepositories().size) - assertEquals(null, repoDao.getRepositories()[0].certificate) - - val cert = getRandomString() - repoDao.updateRepository(repoId, cert) - - assertEquals(1, repoDao.getRepositories().size) - assertEquals(cert, repoDao.getRepositories()[0].certificate) - } -} diff --git a/database/src/dbTest/java/org/fdroid/database/TestUtils.kt b/database/src/dbTest/java/org/fdroid/database/TestUtils.kt index 2ce212fac..ebc90873c 100644 --- a/database/src/dbTest/java/org/fdroid/database/TestUtils.kt +++ b/database/src/dbTest/java/org/fdroid/database/TestUtils.kt @@ -11,10 +11,17 @@ import org.junit.Assert import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue import kotlin.test.fail internal object TestUtils { + fun assertTimestampRecent(timestamp: Long?) { + assertNotNull(timestamp) + assertTrue(System.currentTimeMillis() - timestamp < 2000) + } + fun assertRepoEquals(repoV2: RepoV2, repo: Repository) { val repoId = repo.repoId // mirrors diff --git a/database/src/dbTest/java/org/fdroid/index/v2/IndexV2UpdaterTest.kt b/database/src/dbTest/java/org/fdroid/index/v2/IndexV2UpdaterTest.kt index 03194ed6a..bcc11b7a2 100644 --- a/database/src/dbTest/java/org/fdroid/index/v2/IndexV2UpdaterTest.kt +++ b/database/src/dbTest/java/org/fdroid/index/v2/IndexV2UpdaterTest.kt @@ -10,6 +10,7 @@ import org.fdroid.CompatibilityChecker import org.fdroid.database.DbTest import org.fdroid.database.IndexFormatVersion.TWO import org.fdroid.database.Repository +import org.fdroid.database.TestUtils.assertTimestampRecent import org.fdroid.download.Downloader import org.fdroid.download.DownloaderFactory import org.fdroid.index.IndexUpdateResult @@ -27,6 +28,7 @@ import org.junit.Test import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith import kotlin.test.assertEquals +import kotlin.test.assertNull import kotlin.test.assertTrue import kotlin.test.fail @@ -65,6 +67,7 @@ internal class IndexV2UpdaterTest : DbTest() { val updatedRepo = repoDao.getRepository(repoId) ?: fail() assertEquals(TWO, updatedRepo.formatVersion) assertEquals(CERTIFICATE, updatedRepo.certificate) + assertTimestampRecent(repoDao.getRepository(repoId)?.lastUpdated) } @Test @@ -79,6 +82,7 @@ internal class IndexV2UpdaterTest : DbTest() { val result = indexUpdater.updateNewRepo(repo, FINGERPRINT).noError() assertEquals(IndexUpdateResult.Processed, result) assertDbEquals(repoId, TestDataMidV2.index) + assertTimestampRecent(repoDao.getRepository(repoId)?.lastUpdated) } @Test @@ -93,6 +97,7 @@ internal class IndexV2UpdaterTest : DbTest() { val result = indexUpdater.updateNewRepo(repo, FINGERPRINT).noError() assertEquals(IndexUpdateResult.Processed, result) assertDbEquals(repoId, TestDataMaxV2.index) + assertTimestampRecent(repoDao.getRepository(repoId)?.lastUpdated) } @Test @@ -107,6 +112,7 @@ internal class IndexV2UpdaterTest : DbTest() { val result = indexUpdater.update(repo).noError() assertEquals(IndexUpdateResult.Processed, result) assertDbEquals(repoId, TestDataMidV2.index) + assertTimestampRecent(repoDao.getRepository(repoId)?.lastUpdated) } @Test @@ -122,6 +128,7 @@ internal class IndexV2UpdaterTest : DbTest() { val result = indexUpdater.update(repo).noError() assertEquals(IndexUpdateResult.Processed, result) assertDbEquals(repoId, TestDataMinV2.index) + assertTimestampRecent(repoDao.getRepository(repoId)?.lastUpdated) } @Test @@ -137,6 +144,7 @@ internal class IndexV2UpdaterTest : DbTest() { val result = indexUpdater.update(repo).noError() assertEquals(IndexUpdateResult.Processed, result) assertDbEquals(repoId, TestDataMaxV2.index) + assertTimestampRecent(repoDao.getRepository(repoId)?.lastUpdated) } @Test @@ -152,6 +160,7 @@ internal class IndexV2UpdaterTest : DbTest() { val result = indexUpdater.update(repo).noError() assertEquals(IndexUpdateResult.Unchanged, result) assertDbEquals(repoId, TestDataMinV2.index) + assertNull(repoDao.getRepository(repoId)?.lastUpdated) } @Test diff --git a/database/src/main/java/org/fdroid/database/AppDao.kt b/database/src/main/java/org/fdroid/database/AppDao.kt index b174c45f9..1e85902f8 100644 --- a/database/src/main/java/org/fdroid/database/AppDao.kt +++ b/database/src/main/java/org/fdroid/database/AppDao.kt @@ -96,6 +96,8 @@ public interface AppDao { public fun getInstalledAppListItems(packageManager: PackageManager): LiveData> public fun getNumberOfAppsInCategory(category: String): Int + + public fun getNumberOfAppsInRepository(repoId: Long): Int } public enum class AppListSortOrder { @@ -452,6 +454,9 @@ internal interface AppDaoInt : AppDao { WHERE pref.enabled = 1 AND categories LIKE '%,' || :category || ',%'""") override fun getNumberOfAppsInCategory(category: String): Int + @Query("SELECT COUNT(*) FROM AppMetadata WHERE repoId = :repoId") + override fun getNumberOfAppsInRepository(repoId: Long): Int + @Query("DELETE FROM AppMetadata WHERE repoId = :repoId AND packageId = :packageId") fun deleteAppMetadata(repoId: Long, packageId: String) diff --git a/database/src/main/java/org/fdroid/database/Repository.kt b/database/src/main/java/org/fdroid/database/Repository.kt index e3faadf23..c627db3fe 100644 --- a/database/src/main/java/org/fdroid/database/Repository.kt +++ b/database/src/main/java/org/fdroid/database/Repository.kt @@ -121,8 +121,7 @@ public data class Repository( */ @JvmOverloads public fun getAllMirrors(includeUserMirrors: Boolean = true): List { - // FIXME decide whether we need to add our own address here - return listOf(org.fdroid.download.Mirror(address)) + mirrors.map { + return mirrors.map { it.toDownloadMirror() } + if (includeUserMirrors) userMirrors.map { org.fdroid.download.Mirror(it) diff --git a/database/src/main/java/org/fdroid/database/RepositoryDao.kt b/database/src/main/java/org/fdroid/database/RepositoryDao.kt index 66520fd51..feec65181 100644 --- a/database/src/main/java/org/fdroid/database/RepositoryDao.kt +++ b/database/src/main/java/org/fdroid/database/RepositoryDao.kt @@ -21,30 +21,76 @@ import org.fdroid.index.v2.RepoV2 public interface RepositoryDao { /** * Inserts a new [InitialRepository] from a fixture. + * + * @return the [Repository.repoId] of the inserted repo. */ - public fun insert(initialRepo: InitialRepository) + public fun insert(initialRepo: InitialRepository): Long /** - * Removes all repos and their preferences. + * Inserts an empty [Repository] for an initial update. + * + * @return the [Repository.repoId] of the inserted repo. */ - public fun clearAll() - - public fun getRepository(repoId: Long): Repository? public fun insertEmptyRepo( address: String, username: String? = null, password: String? = null, ): Long - public fun deleteRepository(repoId: Long) + /** + * Returns the repository with the given [repoId] or null, if none was found with that ID. + */ + public fun getRepository(repoId: Long): Repository? + + /** + * Returns a list of all [Repository]s in the database. + */ public fun getRepositories(): List + + /** + * Same as [getRepositories], but does return a [LiveData]. + */ public fun getLiveRepositories(): LiveData> - public fun countAppsPerRepository(repoId: Long): Int - public fun setRepositoryEnabled(repoId: Long, enabled: Boolean) - public fun updateUserMirrors(repoId: Long, mirrors: List) - public fun updateUsernameAndPassword(repoId: Long, username: String?, password: String?) - public fun updateDisabledMirrors(repoId: Long, disabledMirrors: List) + + /** + * Returns a live data of all categories declared by all [Repository]s. + */ public fun getLiveCategories(): LiveData> + + /** + * Enables or disables the repository with the given [repoId]. + * Data from disabled repositories is ignored in many queries. + */ + public fun setRepositoryEnabled(repoId: Long, enabled: Boolean) + + /** + * Updates the user-defined mirrors of the repository with the given [repoId]. + * The existing mirrors get overwritten with the given [mirrors]. + */ + public fun updateUserMirrors(repoId: Long, mirrors: List) + + /** + * Updates the user name and password (for basic authentication) + * of the repository with the given [repoId]. + * The existing user name and password get overwritten with the given [username] and [password]. + */ + public fun updateUsernameAndPassword(repoId: Long, username: String?, password: String?) + + /** + * Updates the disabled mirrors of the repository with the given [repoId]. + * The existing disabled mirrors get overwritten with the given [disabledMirrors]. + */ + public fun updateDisabledMirrors(repoId: Long, disabledMirrors: List) + + /** + * Removes a [Repository] with the given [repoId] with all associated data from the database. + */ + public fun deleteRepository(repoId: Long) + + /** + * Removes all repos and their preferences. + */ + public fun clearAll() } @Dao @@ -72,7 +118,7 @@ internal interface RepositoryDaoInt : RepositoryDao { fun insert(repositoryPreferences: RepositoryPreferences) @Transaction - override fun insert(initialRepo: InitialRepository) { + override fun insert(initialRepo: InitialRepository): Long { val repo = CoreRepository( name = mapOf("en-US" to initialRepo.name), address = initialRepo.address, @@ -92,6 +138,7 @@ internal interface RepositoryDaoInt : RepositoryDao { enabled = initialRepo.enabled, ) insert(repositoryPreferences) + return repoId } @Transaction @@ -125,37 +172,38 @@ internal interface RepositoryDaoInt : RepositoryDao { @Transaction @VisibleForTesting - fun insertOrReplace(repository: RepoV2): Long { - val repoId = insertOrReplace(repository.toCoreRepository(version = 0)) - insertRepositoryPreferences(repoId) + fun insertOrReplace(repository: RepoV2, version: Long = 0): Long { + val repoId = insertOrReplace(repository.toCoreRepository(version = version)) + val currentMaxWeight = getMaxRepositoryWeight() + val repositoryPreferences = RepositoryPreferences(repoId, currentMaxWeight + 1) + insert(repositoryPreferences) insertRepoTables(repoId, repository) return repoId } - private fun insertRepositoryPreferences(repoId: Long) { - val currentMaxWeight = getMaxRepositoryWeight() - val repositoryPreferences = RepositoryPreferences(repoId, currentMaxWeight + 1) - insert(repositoryPreferences) - } - - /** - * Use when replacing an existing repo with a full index. - * This removes all existing index data associated with this repo from the database, - * but does not touch repository preferences. - * @throws IllegalStateException if no repo with the given [repoId] exists. - */ - @Transaction - fun clear(repoId: Long) { - val repo = getRepository(repoId) ?: error("repo with id $repoId does not exist") - // this clears all foreign key associated data since the repo gets replaced - insertOrReplace(repo.repository) - } + @Query("SELECT MAX(weight) FROM RepositoryPreferences") + fun getMaxRepositoryWeight(): Int @Transaction - override fun clearAll() { - deleteAllCoreRepositories() - deleteAllRepositoryPreferences() - } + @Query("SELECT * FROM CoreRepository WHERE repoId = :repoId") + override fun getRepository(repoId: Long): Repository? + + @Transaction + @Query("SELECT * FROM CoreRepository") + override fun getRepositories(): List + + @Transaction + @Query("SELECT * FROM CoreRepository") + override fun getLiveRepositories(): LiveData> + + @Query("SELECT * FROM RepositoryPreferences WHERE repoId = :repoId") + fun getRepositoryPreferences(repoId: Long): RepositoryPreferences? + + @RewriteQueriesToDropUnusedColumns + @Query("""SELECT * FROM Category + JOIN RepositoryPreferences AS pref USING (repoId) + WHERE pref.enabled = 1 GROUP BY id HAVING MAX(pref.weight)""") + override fun getLiveCategories(): LiveData> /** * Updates an existing repo with new data from a full index update. @@ -180,10 +228,25 @@ internal interface RepositoryDaoInt : RepositoryDao { insertReleaseChannels(repository.releaseChannels.toRepoReleaseChannel(repoId)) } - @Transaction - @Query("SELECT * FROM CoreRepository WHERE repoId = :repoId") - override fun getRepository(repoId: Long): Repository? + @Update + fun updateRepository(repo: CoreRepository): Int + /** + * Updates the certificate for the [Repository] with the given [repoId]. + * This should be used for V1 index updating where we only get the full cert + * after reading the entire index file. + * V2 index should use [update] instead as there the certificate is known + * before reading full index. + */ + @Query("UPDATE CoreRepository SET certificate = :certificate WHERE repoId = :repoId") + fun updateRepository(repoId: Long, certificate: String) + + @Update + fun updateRepositoryPreferences(preferences: RepositoryPreferences) + + /** + * Used to update an existing repository with a given [jsonObject] JSON diff. + */ @Transaction fun updateRepository(repoId: Long, version: Long, jsonObject: JsonObject) { // get existing repo @@ -237,15 +300,6 @@ internal interface RepositoryDaoInt : RepositoryDao { ) } - @Update - fun updateRepository(repo: CoreRepository): Int - - @Query("UPDATE CoreRepository SET certificate = :certificate WHERE repoId = :repoId") - fun updateRepository(repoId: Long, certificate: String) - - @Update - fun updateRepositoryPreferences(preferences: RepositoryPreferences) - @Query("UPDATE RepositoryPreferences SET enabled = :enabled WHERE repoId = :repoId") override fun setRepositoryEnabled(repoId: Long, enabled: Boolean) @@ -260,73 +314,6 @@ internal interface RepositoryDaoInt : RepositoryDao { WHERE repoId = :repoId""") override fun updateDisabledMirrors(repoId: Long, disabledMirrors: List) - @Transaction - @Query("SELECT * FROM CoreRepository") - override fun getRepositories(): List - - @Transaction - @Query("SELECT * FROM CoreRepository") - override fun getLiveRepositories(): LiveData> - - @VisibleForTesting - @Query("SELECT * FROM Mirror") - fun getMirrors(): List - - @VisibleForTesting - @Query("DELETE FROM Mirror WHERE repoId = :repoId") - fun deleteMirrors(repoId: Long) - - @VisibleForTesting - @Query("SELECT * FROM AntiFeature") - fun getAntiFeatures(): List - - @Query("SELECT * FROM RepositoryPreferences WHERE repoId = :repoId") - fun getRepositoryPreferences(repoId: Long): RepositoryPreferences? - - @Query("SELECT MAX(weight) FROM RepositoryPreferences") - fun getMaxRepositoryWeight(): Int - - @VisibleForTesting - @Query("DELETE FROM AntiFeature WHERE repoId = :repoId") - fun deleteAntiFeatures(repoId: Long) - - @VisibleForTesting - @Query("DELETE FROM AntiFeature WHERE repoId = :repoId AND id = :id") - fun deleteAntiFeature(repoId: Long, id: String) - - @VisibleForTesting - @Query("SELECT * FROM Category") - fun getCategories(): List - - @RewriteQueriesToDropUnusedColumns - @Query("""SELECT * FROM Category - JOIN RepositoryPreferences AS pref USING (repoId) - WHERE pref.enabled = 1 GROUP BY id HAVING MAX(pref.weight)""") - override fun getLiveCategories(): LiveData> - - @Query("SELECT COUNT(*) FROM AppMetadata WHERE repoId = :repoId") - override fun countAppsPerRepository(repoId: Long): Int - - @VisibleForTesting - @Query("DELETE FROM Category WHERE repoId = :repoId") - fun deleteCategories(repoId: Long) - - @VisibleForTesting - @Query("DELETE FROM Category WHERE repoId = :repoId AND id = :id") - fun deleteCategory(repoId: Long, id: String) - - @VisibleForTesting - @Query("SELECT * FROM ReleaseChannel") - fun getReleaseChannels(): List - - @VisibleForTesting - @Query("DELETE FROM ReleaseChannel WHERE repoId = :repoId") - fun deleteReleaseChannels(repoId: Long) - - @VisibleForTesting - @Query("DELETE FROM ReleaseChannel WHERE repoId = :repoId AND id = :id") - fun deleteReleaseChannel(repoId: Long, id: String) - @Transaction override fun deleteRepository(repoId: Long) { deleteCoreRepository(repoId) @@ -338,13 +325,90 @@ internal interface RepositoryDaoInt : RepositoryDao { @Query("DELETE FROM CoreRepository WHERE repoId = :repoId") fun deleteCoreRepository(repoId: Long) - @Query("DELETE FROM CoreRepository") - fun deleteAllCoreRepositories() - @Query("DELETE FROM RepositoryPreferences WHERE repoId = :repoId") fun deleteRepositoryPreferences(repoId: Long) + @Query("DELETE FROM CoreRepository") + fun deleteAllCoreRepositories() + @Query("DELETE FROM RepositoryPreferences") fun deleteAllRepositoryPreferences() + /** + * Used for diffing. + */ + @Query("DELETE FROM Mirror WHERE repoId = :repoId") + fun deleteMirrors(repoId: Long) + + /** + * Used for diffing. + */ + @Query("DELETE FROM AntiFeature WHERE repoId = :repoId") + fun deleteAntiFeatures(repoId: Long) + + /** + * Used for diffing. + */ + @Query("DELETE FROM AntiFeature WHERE repoId = :repoId AND id = :id") + fun deleteAntiFeature(repoId: Long, id: String) + + /** + * Used for diffing. + */ + @Query("DELETE FROM Category WHERE repoId = :repoId") + fun deleteCategories(repoId: Long) + + /** + * Used for diffing. + */ + @Query("DELETE FROM Category WHERE repoId = :repoId AND id = :id") + fun deleteCategory(repoId: Long, id: String) + + /** + * Used for diffing. + */ + @Query("DELETE FROM ReleaseChannel WHERE repoId = :repoId") + fun deleteReleaseChannels(repoId: Long) + + /** + * Used for diffing. + */ + @Query("DELETE FROM ReleaseChannel WHERE repoId = :repoId AND id = :id") + fun deleteReleaseChannel(repoId: Long, id: String) + + /** + * Use when replacing an existing repo with a full index. + * This removes all existing index data associated with this repo from the database, + * but does not touch repository preferences. + * @throws IllegalStateException if no repo with the given [repoId] exists. + */ + @Transaction + fun clear(repoId: Long) { + val repo = getRepository(repoId) ?: error("repo with id $repoId does not exist") + // this clears all foreign key associated data since the repo gets replaced + insertOrReplace(repo.repository) + } + + @Transaction + override fun clearAll() { + deleteAllCoreRepositories() + deleteAllRepositoryPreferences() + } + + @VisibleForTesting + @Query("SELECT COUNT(*) FROM Mirror") + fun countMirrors(): Int + + @VisibleForTesting + @Query("SELECT COUNT(*) FROM AntiFeature") + fun countAntiFeatures(): Int + + @VisibleForTesting + @Query("SELECT COUNT(*) FROM Category") + fun countCategories(): Int + + @VisibleForTesting + @Query("SELECT COUNT(*) FROM ReleaseChannel") + fun countReleaseChannels(): Int + }