From 7fbb08de1befbddf09515afd8880eeb7622313f8 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 28 Apr 2022 08:47:01 -0300 Subject: [PATCH] [db] explicitly clear repo data before updating with full index In JSON, keys can come in any order, so we need to handle the case that we receive packages before the repo metadata. Now we explicitly clear data and rename the insert method to insertOrReplace in order to make it clear that data gets replaced. Also, we pass the repoId into the constructor of the DB stream receivers to make clear that one receiver is meant to receive a single pre-existing repo. --- .../java/org/fdroid/database/AppTest.kt | 10 +-- .../org/fdroid/database/IndexV1InsertTest.kt | 68 ++++++++++++++++--- .../org/fdroid/database/IndexV2InsertTest.kt | 23 ++++--- .../org/fdroid/database/RepositoryDiffTest.kt | 6 +- .../org/fdroid/database/RepositoryTest.kt | 18 +++-- .../org/fdroid/database/UpdateCheckerTest.kt | 8 +-- .../java/org/fdroid/database/VersionTest.kt | 4 +- .../org/fdroid/database/DbStreamReceiver.kt | 33 --------- .../org/fdroid/database/DbV1StreamReceiver.kt | 18 +++-- .../org/fdroid/database/DbV2StreamReceiver.kt | 52 ++++++++++++++ .../java/org/fdroid/database/RepositoryDao.kt | 34 +++++++--- .../org/fdroid/index/v1/IndexV1Updater.kt | 4 +- 12 files changed, 184 insertions(+), 94 deletions(-) delete mode 100644 database/src/main/java/org/fdroid/database/DbStreamReceiver.kt create mode 100644 database/src/main/java/org/fdroid/database/DbV2StreamReceiver.kt diff --git a/database/src/androidTest/java/org/fdroid/database/AppTest.kt b/database/src/androidTest/java/org/fdroid/database/AppTest.kt index 295fc1672..b2b4e69f7 100644 --- a/database/src/androidTest/java/org/fdroid/database/AppTest.kt +++ b/database/src/androidTest/java/org/fdroid/database/AppTest.kt @@ -28,7 +28,7 @@ internal class AppTest : DbTest() { @Test fun insertGetDeleteSingleApp() { - val repoId = repoDao.insert(getRandomRepo()) + val repoId = repoDao.insertOrReplace(getRandomRepo()) val metadataV2 = getRandomMetadataV2() appDao.insert(repoId, packageId, metadataV2) @@ -53,7 +53,7 @@ internal class AppTest : DbTest() { @Test fun testAppOverViewItem() { - val repoId = repoDao.insert(getRandomRepo()) + val repoId = repoDao.insertOrReplace(getRandomRepo()) val packageId1 = getRandomString() val packageId2 = getRandomString() val packageId3 = getRandomString() @@ -90,7 +90,7 @@ internal class AppTest : DbTest() { assertNull(apps3.find { it.packageId == packageId3 }) // app4 is the same as app1 and thus will not be shown again - val repoId2 = repoDao.insert(getRandomRepo()) + val repoId2 = repoDao.insertOrReplace(getRandomRepo()) val app4 = getRandomMetadataV2().copy(name = name2, icon = icons2) appDao.insert(repoId2, packageId1, app4) val apps4 = appDao.getAppOverviewItems().getOrAwaitValue() ?: fail() @@ -99,8 +99,8 @@ internal class AppTest : DbTest() { @Test fun testAppByRepoWeight() { - val repoId1 = repoDao.insert(getRandomRepo()) - val repoId2 = repoDao.insert(getRandomRepo()) + val repoId1 = repoDao.insertOrReplace(getRandomRepo()) + val repoId2 = repoDao.insertOrReplace(getRandomRepo()) val metadata1 = getRandomMetadataV2() val metadata2 = metadata1.copy(lastUpdated = metadata1.lastUpdated + 1) diff --git a/database/src/androidTest/java/org/fdroid/database/IndexV1InsertTest.kt b/database/src/androidTest/java/org/fdroid/database/IndexV1InsertTest.kt index 58259a64b..48b9b76cd 100644 --- a/database/src/androidTest/java/org/fdroid/database/IndexV1InsertTest.kt +++ b/database/src/androidTest/java/org/fdroid/database/IndexV1InsertTest.kt @@ -6,8 +6,16 @@ import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.serialization.SerializationException import org.apache.commons.io.input.CountingInputStream +import org.fdroid.CompatibilityChecker import org.fdroid.index.v1.IndexV1StreamProcessor -import org.fdroid.index.v2.IndexStreamProcessor +import org.fdroid.index.v1.IndexV1StreamReceiver +import org.fdroid.index.v2.AntiFeatureV2 +import org.fdroid.index.v2.CategoryV2 +import org.fdroid.index.v2.IndexV2StreamProcessor +import org.fdroid.index.v2.MetadataV2 +import org.fdroid.index.v2.PackageVersionV2 +import org.fdroid.index.v2.ReleaseChannelV2 +import org.fdroid.index.v2.RepoV2 import org.junit.Test import org.junit.runner.RunWith import kotlin.math.roundToInt @@ -25,7 +33,8 @@ internal class IndexV1InsertTest : DbTest() { val fileSize = c.resources.assets.openFd("index-v1.json").use { it.length } val inputStream = CountingInputStream(c.resources.assets.open("index-v1.json")) var currentByteCount: Long = 0 - val indexProcessor = IndexV1StreamProcessor(DbV1StreamReceiver(db) { true }, null) { + val repoId = db.getRepositoryDao().insertEmptyRepo("https://f-droid.org/repo") + val streamReceiver = TestStreamReceiver(repoId) { val bytesRead = inputStream.byteCount val bytesSinceLastCall = bytesRead - currentByteCount if (bytesSinceLastCall > 0) { @@ -36,13 +45,12 @@ internal class IndexV1InsertTest : DbTest() { // the stream gets read in big chunks, but ensure they are not too big, e.g. entire file assertTrue(bytesSinceLastCall < 600_000, "$bytesSinceLastCall") currentByteCount = bytesRead - bytesRead } + val indexProcessor = IndexV1StreamProcessor(streamReceiver, null) db.runInTransaction { - val repoId = db.getRepositoryDao().insertEmptyRepo("https://f-droid.org/repo") inputStream.use { indexStream -> - indexProcessor.process(repoId, indexStream) + indexProcessor.process(indexStream) } } assertTrue(repoDao.getRepositories().size == 1) @@ -112,11 +120,11 @@ internal class IndexV1InsertTest : DbTest() { private fun insertV2ForComparison(version: Int) { val c = getApplicationContext() val inputStream = CountingInputStream(c.resources.assets.open("index-v2.json")) - val indexProcessor = IndexStreamProcessor(DbStreamReceiver(db) { true }, null) + val repoId = db.getRepositoryDao().insertEmptyRepo("https://f-droid.org/repo") + val indexProcessor = IndexV2StreamProcessor(DbV2StreamReceiver(db, { true }, repoId), null) db.runInTransaction { - val repoId = db.getRepositoryDao().insertEmptyRepo("https://f-droid.org/repo") inputStream.use { indexStream -> - indexProcessor.process(repoId, version, indexStream) + indexProcessor.process(version, indexStream) } } } @@ -125,15 +133,17 @@ internal class IndexV1InsertTest : DbTest() { fun testExceptionWhileStreamingDoesNotSaveIntoDb() { val c = getApplicationContext() val cIn = CountingInputStream(c.resources.assets.open("index-v1.json")) - val indexProcessor = IndexStreamProcessor(DbStreamReceiver(db) { true }, null) { + val compatibilityChecker = CompatibilityChecker { if (cIn.byteCount > 824096) throw SerializationException() - cIn.byteCount + true } + val indexProcessor = + IndexV2StreamProcessor(DbV2StreamReceiver(db, compatibilityChecker, 1), null) assertFailsWith { db.runInTransaction { cIn.use { indexStream -> - indexProcessor.process(1, 42, indexStream) + indexProcessor.process(42, indexStream) } } } @@ -145,4 +155,40 @@ internal class IndexV1InsertTest : DbTest() { assertTrue(versionDao.countVersionedStrings() == 0) } + inner class TestStreamReceiver( + repoId: Long, + private val callback: () -> Unit, + ) : IndexV1StreamReceiver { + private val streamReceiver = DbV1StreamReceiver(db, { true }, repoId) + override fun receive(repo: RepoV2, version: Int, certificate: String?) { + streamReceiver.receive(repo, version, certificate) + callback() + } + + override fun receive(packageId: String, m: MetadataV2) { + streamReceiver.receive(packageId, m) + callback() + } + + override fun receive(packageId: String, v: Map) { + streamReceiver.receive(packageId, v) + callback() + } + + override fun updateRepo( + antiFeatures: Map, + categories: Map, + releaseChannels: Map, + ) { + streamReceiver.updateRepo(antiFeatures, categories, releaseChannels) + callback() + } + + override fun updateAppMetadata(packageId: String, preferredSigner: String?) { + streamReceiver.updateAppMetadata(packageId, preferredSigner) + callback() + } + + } + } diff --git a/database/src/androidTest/java/org/fdroid/database/IndexV2InsertTest.kt b/database/src/androidTest/java/org/fdroid/database/IndexV2InsertTest.kt index 141a6cc45..3446dee29 100644 --- a/database/src/androidTest/java/org/fdroid/database/IndexV2InsertTest.kt +++ b/database/src/androidTest/java/org/fdroid/database/IndexV2InsertTest.kt @@ -6,7 +6,8 @@ import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.serialization.SerializationException import org.apache.commons.io.input.CountingInputStream -import org.fdroid.index.v2.IndexStreamProcessor +import org.fdroid.CompatibilityChecker +import org.fdroid.index.v2.IndexV2StreamProcessor import org.junit.Test import org.junit.runner.RunWith import kotlin.math.roundToInt @@ -22,7 +23,7 @@ internal class IndexV2InsertTest : DbTest() { val fileSize = c.resources.assets.openFd("index-v2.json").use { it.length } val inputStream = CountingInputStream(c.resources.assets.open("index-v2.json")) var currentByteCount: Long = 0 - val indexProcessor = IndexStreamProcessor(DbStreamReceiver(db) { true }, null) { + val compatibilityChecker = CompatibilityChecker { val bytesRead = inputStream.byteCount val bytesSinceLastCall = bytesRead - currentByteCount if (bytesSinceLastCall > 0) { @@ -33,13 +34,15 @@ internal class IndexV2InsertTest : DbTest() { // the stream gets read in big chunks, but ensure they are not too big, e.g. entire file assertTrue(bytesSinceLastCall < 400_000, "$bytesSinceLastCall") currentByteCount = bytesRead - bytesRead + true } + val repoId = db.getRepositoryDao().insertEmptyRepo("https://f-droid.org/repo") + val streamReceiver = DbV2StreamReceiver(db, compatibilityChecker, repoId) + val indexProcessor = IndexV2StreamProcessor(streamReceiver, null) db.runInTransaction { - val repoId = db.getRepositoryDao().insertEmptyRepo("https://f-droid.org/repo") inputStream.use { indexStream -> - indexProcessor.process(repoId, 42, indexStream) + indexProcessor.process(42, indexStream) } } assertTrue(repoDao.getRepositories().size == 1) @@ -60,15 +63,17 @@ internal class IndexV2InsertTest : DbTest() { fun testExceptionWhileStreamingDoesNotSaveIntoDb() { val c = getApplicationContext() val cIn = CountingInputStream(c.resources.assets.open("index-v2.json")) - val indexProcessor = IndexStreamProcessor(DbStreamReceiver(db) { true }, null) { + val compatibilityChecker = CompatibilityChecker { if (cIn.byteCount > 824096) throw SerializationException() - cIn.byteCount + true } - assertFailsWith { db.runInTransaction { + val repoId = db.getRepositoryDao().insertEmptyRepo("http://example.org") + val streamReceiver = DbV2StreamReceiver(db, compatibilityChecker, repoId) + val indexProcessor = IndexV2StreamProcessor(streamReceiver, null) cIn.use { indexStream -> - indexProcessor.process(1, 42, indexStream) + indexProcessor.process(42, indexStream) } } } diff --git a/database/src/androidTest/java/org/fdroid/database/RepositoryDiffTest.kt b/database/src/androidTest/java/org/fdroid/database/RepositoryDiffTest.kt index 4fa401a6d..962e86b4d 100644 --- a/database/src/androidTest/java/org/fdroid/database/RepositoryDiffTest.kt +++ b/database/src/androidTest/java/org/fdroid/database/RepositoryDiffTest.kt @@ -48,10 +48,10 @@ internal class RepositoryDiffTest : DbTest() { fun timestampDiffTwoReposInDb() { // insert repo val repo = getRandomRepo() - repoDao.insert(repo) + repoDao.insertOrReplace(repo) // insert another repo before updating - repoDao.insert(getRandomRepo()) + repoDao.insertOrReplace(getRandomRepo()) // check that the repo got added and retrieved as expected var repos = repoDao.getRepositories().sortedBy { it.repoId } @@ -244,7 +244,7 @@ internal class RepositoryDiffTest : DbTest() { private fun testDiff(repo: RepoV2, json: String, repoChecker: (List) -> Unit) { // insert repo - repoDao.insert(repo) + repoDao.insertOrReplace(repo) // check that the repo got added and retrieved as expected var repos = repoDao.getRepositories() diff --git a/database/src/androidTest/java/org/fdroid/database/RepositoryTest.kt b/database/src/androidTest/java/org/fdroid/database/RepositoryTest.kt index be4ed360d..dd9e189bb 100644 --- a/database/src/androidTest/java/org/fdroid/database/RepositoryTest.kt +++ b/database/src/androidTest/java/org/fdroid/database/RepositoryTest.kt @@ -20,7 +20,7 @@ internal class RepositoryTest : DbTest() { fun insertAndDeleteTwoRepos() { // insert first repo val repo1 = getRandomRepo() - val repoId1 = repoDao.insert(repo1) + val repoId1 = repoDao.insertOrReplace(repo1) // check that first repo got added and retrieved as expected var repos = repoDao.getRepositories() @@ -31,7 +31,7 @@ internal class RepositoryTest : DbTest() { // insert second repo val repo2 = getRandomRepo() - val repoId2 = repoDao.insert(repo2) + val repoId2 = repoDao.insertOrReplace(repo2) // check that both repos got added and retrieved as expected repos = repoDao.getRepositories().sortedBy { it.repoId } @@ -58,8 +58,8 @@ internal class RepositoryTest : DbTest() { } @Test - fun replacingRepoRemovesAllAssociatedData() { - val repoId = repoDao.insert(getRandomRepo()) + fun clearingRepoRemovesAllAssociatedData() { + val repoId = repoDao.insertOrReplace(getRandomRepo()) val repositoryPreferences = repoDao.getRepositoryPreferences(repoId) val packageId = getRandomString() val versionId = getRandomString() @@ -72,22 +72,20 @@ internal class RepositoryTest : DbTest() { assertEquals(1, versionDao.getAppVersions(repoId, packageId).size) assertTrue(versionDao.getVersionedStrings(repoId, packageId).isNotEmpty()) - val cert = getRandomString() - repoDao.replace(repoId, getRandomRepo(), 42, cert) + repoDao.clear(repoId) assertEquals(1, repoDao.getRepositories().size) assertEquals(0, appDao.getAppMetadata().size) assertEquals(0, appDao.getLocalizedFiles().size) assertEquals(0, appDao.getLocalizedFileLists().size) assertEquals(0, versionDao.getAppVersions(repoId, packageId).size) assertEquals(0, versionDao.getVersionedStrings(repoId, packageId).size) - - assertEquals(cert, repoDao.getRepository(repoId)?.certificate) + // preferences are not touched by clearing assertEquals(repositoryPreferences, repoDao.getRepositoryPreferences(repoId)) } @Test - fun certGetsUpdates() { - val repoId = repoDao.insert(getRandomRepo()) + fun certGetsUpdated() { + val repoId = repoDao.insertOrReplace(getRandomRepo()) assertEquals(1, repoDao.getRepositories().size) assertEquals(null, repoDao.getRepositories()[0].certificate) diff --git a/database/src/androidTest/java/org/fdroid/database/UpdateCheckerTest.kt b/database/src/androidTest/java/org/fdroid/database/UpdateCheckerTest.kt index 07d84215c..9cd38ae1d 100644 --- a/database/src/androidTest/java/org/fdroid/database/UpdateCheckerTest.kt +++ b/database/src/androidTest/java/org/fdroid/database/UpdateCheckerTest.kt @@ -25,16 +25,16 @@ internal class UpdateCheckerTest : DbTest() { updateChecker = UpdateChecker(db, context.packageManager) } - @OptIn(ExperimentalTime::class) @Test + @OptIn(ExperimentalTime::class) fun testGetUpdates() { val inputStream = CountingInputStream(context.resources.assets.open("index-v1.json")) - val indexProcessor = IndexV1StreamProcessor(DbV1StreamReceiver(db) { true }, null) + val repoId = db.getRepositoryDao().insertEmptyRepo("https://f-droid.org/repo") + val indexProcessor = IndexV1StreamProcessor(DbV1StreamReceiver(db, { true }, repoId), null) db.runInTransaction { - val repoId = db.getRepositoryDao().insertEmptyRepo("https://f-droid.org/repo") inputStream.use { indexStream -> - indexProcessor.process(repoId, indexStream) + indexProcessor.process(indexStream) } } diff --git a/database/src/androidTest/java/org/fdroid/database/VersionTest.kt b/database/src/androidTest/java/org/fdroid/database/VersionTest.kt index 5db57b37e..8468685b5 100644 --- a/database/src/androidTest/java/org/fdroid/database/VersionTest.kt +++ b/database/src/androidTest/java/org/fdroid/database/VersionTest.kt @@ -25,7 +25,7 @@ internal class VersionTest : DbTest() { @Test fun insertGetDeleteSingleVersion() { - val repoId = repoDao.insert(getRandomRepo()) + val repoId = repoDao.insertOrReplace(getRandomRepo()) appDao.insert(repoId, packageId, getRandomMetadataV2()) val packageVersion = getRandomPackageVersionV2() val isCompatible = Random.nextBoolean() @@ -57,7 +57,7 @@ internal class VersionTest : DbTest() { @Test fun insertGetDeleteTwoVersions() { // insert two versions along with required objects - val repoId = repoDao.insert(getRandomRepo()) + val repoId = repoDao.insertOrReplace(getRandomRepo()) appDao.insert(repoId, packageId, getRandomMetadataV2()) val packageVersion1 = getRandomPackageVersionV2() val packageVersion2 = getRandomPackageVersionV2() diff --git a/database/src/main/java/org/fdroid/database/DbStreamReceiver.kt b/database/src/main/java/org/fdroid/database/DbStreamReceiver.kt deleted file mode 100644 index b5304505e..000000000 --- a/database/src/main/java/org/fdroid/database/DbStreamReceiver.kt +++ /dev/null @@ -1,33 +0,0 @@ -package org.fdroid.database - -import android.content.res.Resources -import androidx.core.os.ConfigurationCompat.getLocales -import androidx.core.os.LocaleListCompat -import org.fdroid.CompatibilityChecker -import org.fdroid.index.v2.IndexStreamReceiver -import org.fdroid.index.v2.PackageV2 -import org.fdroid.index.v2.RepoV2 - -internal class DbStreamReceiver( - private val db: FDroidDatabaseInt, - private val compatibilityChecker: CompatibilityChecker, -) : IndexStreamReceiver { - - private val locales: LocaleListCompat = getLocales(Resources.getSystem().configuration) - - override fun receive(repoId: Long, repo: RepoV2, version: Int, certificate: String?) { - db.getRepositoryDao().replace(repoId, repo, version, certificate) - } - - override fun receive(repoId: Long, packageId: String, p: PackageV2) { - db.getAppDao().insert(repoId, packageId, p.metadata, locales) - db.getVersionDao().insert(repoId, packageId, p.versions) { - compatibilityChecker.isCompatible(it.manifest) - } - } - - override fun onStreamEnded(repoId: Long) { - db.afterUpdatingRepo(repoId) - } - -} diff --git a/database/src/main/java/org/fdroid/database/DbV1StreamReceiver.kt b/database/src/main/java/org/fdroid/database/DbV1StreamReceiver.kt index 84cd5231a..5a0cbe8cd 100644 --- a/database/src/main/java/org/fdroid/database/DbV1StreamReceiver.kt +++ b/database/src/main/java/org/fdroid/database/DbV1StreamReceiver.kt @@ -12,29 +12,35 @@ import org.fdroid.index.v2.PackageVersionV2 import org.fdroid.index.v2.ReleaseChannelV2 import org.fdroid.index.v2.RepoV2 +/** + * Note that this class expects that its [receive] method with [RepoV2] gets called first. + * A different order of calls is not supported. + */ +@Deprecated("Use DbV2StreamReceiver instead") internal class DbV1StreamReceiver( private val db: FDroidDatabaseInt, private val compatibilityChecker: CompatibilityChecker, + private val repoId: Long, ) : IndexV1StreamReceiver { private val locales: LocaleListCompat = getLocales(Resources.getSystem().configuration) - override fun receive(repoId: Long, repo: RepoV2, version: Int, certificate: String?) { - db.getRepositoryDao().replace(repoId, repo, version, certificate) + override fun receive(repo: RepoV2, version: Int, certificate: String?) { + db.getRepositoryDao().clear(repoId) + db.getRepositoryDao().update(repoId, repo, version, certificate) } - override fun receive(repoId: Long, packageId: String, m: MetadataV2) { + override fun receive(packageId: String, m: MetadataV2) { db.getAppDao().insert(repoId, packageId, m, locales) } - override fun receive(repoId: Long, packageId: String, v: Map) { + override fun receive(packageId: String, v: Map) { db.getVersionDao().insert(repoId, packageId, v) { compatibilityChecker.isCompatible(it.manifest) } } override fun updateRepo( - repoId: Long, antiFeatures: Map, categories: Map, releaseChannels: Map, @@ -47,7 +53,7 @@ internal class DbV1StreamReceiver( db.afterUpdatingRepo(repoId) } - override fun updateAppMetadata(repoId: Long, packageId: String, preferredSigner: String?) { + override fun updateAppMetadata(packageId: String, preferredSigner: String?) { db.getAppDao().updatePreferredSigner(repoId, packageId, preferredSigner) } diff --git a/database/src/main/java/org/fdroid/database/DbV2StreamReceiver.kt b/database/src/main/java/org/fdroid/database/DbV2StreamReceiver.kt new file mode 100644 index 000000000..c06d612a9 --- /dev/null +++ b/database/src/main/java/org/fdroid/database/DbV2StreamReceiver.kt @@ -0,0 +1,52 @@ +package org.fdroid.database + +import android.content.res.Resources +import androidx.core.os.ConfigurationCompat.getLocales +import androidx.core.os.LocaleListCompat +import org.fdroid.CompatibilityChecker +import org.fdroid.index.v2.IndexV2StreamReceiver +import org.fdroid.index.v2.PackageV2 +import org.fdroid.index.v2.RepoV2 + +internal class DbV2StreamReceiver( + private val db: FDroidDatabaseInt, + private val compatibilityChecker: CompatibilityChecker, + private val repoId: Long, +) : IndexV2StreamReceiver { + + private val locales: LocaleListCompat = getLocales(Resources.getSystem().configuration) + private var clearedRepoData = false + + @Synchronized + override fun receive(repo: RepoV2, version: Int, certificate: String?) { + clearRepoDataIfNeeded() + db.getRepositoryDao().update(repoId, repo, version, certificate) + } + + @Synchronized + override fun receive(packageId: String, p: PackageV2) { + clearRepoDataIfNeeded() + db.getAppDao().insert(repoId, packageId, p.metadata, locales) + db.getVersionDao().insert(repoId, packageId, p.versions) { + compatibilityChecker.isCompatible(it.manifest) + } + } + + @Synchronized + override fun onStreamEnded() { + db.afterUpdatingRepo(repoId) + } + + /** + * As it is a valid index to receive packages before the repo, + * we can not clear all repo data when receiving the repo, + * but need to do it once at the beginning. + */ + private fun clearRepoDataIfNeeded() { + if (!clearedRepoData) { + db.getRepositoryDao().clear(repoId) + clearedRepoData = true + } + } + +} diff --git a/database/src/main/java/org/fdroid/database/RepositoryDao.kt b/database/src/main/java/org/fdroid/database/RepositoryDao.kt index 8dc64af5e..ccb8815c9 100644 --- a/database/src/main/java/org/fdroid/database/RepositoryDao.kt +++ b/database/src/main/java/org/fdroid/database/RepositoryDao.kt @@ -25,8 +25,15 @@ public interface RepositoryDao { /** * Use when replacing an existing repo with a full index. * This removes all existing index data associated with this repo from the database. + * @throws IllegalStateException if no repo with the given [repoId] exists. */ - public fun replace(repoId: Long, repository: RepoV2, version: Int, certificate: String?) + public fun clear(repoId: Long) + + /** + * Updates an existing repo with new data from a full index update. + * Call [clear] first to ensure old data was removed. + */ + public fun update(repoId: Long, repository: RepoV2, version: Int, certificate: String?) public fun getRepository(repoId: Long): Repository? public fun insertEmptyRepo( @@ -50,7 +57,10 @@ public interface RepositoryDao { internal interface RepositoryDaoInt : RepositoryDao { @Insert(onConflict = REPLACE) - fun insert(repository: CoreRepository): Long + fun insertOrReplace(repository: CoreRepository): Long + + @Update + fun update(repository: CoreRepository) @Insert(onConflict = REPLACE) fun insertMirrors(mirrors: List) @@ -78,7 +88,7 @@ internal interface RepositoryDaoInt : RepositoryDao { description = mapOf("en-US" to initialRepo.description), certificate = initialRepo.certificate, ) - val repoId = insert(repo) + val repoId = insertOrReplace(repo) val repositoryPreferences = RepositoryPreferences( repoId = repoId, weight = initialRepo.weight, @@ -102,7 +112,7 @@ internal interface RepositoryDaoInt : RepositoryDao { version = null, certificate = null, ) - val repoId = insert(repo) + val repoId = insertOrReplace(repo) val currentMaxWeight = getMaxRepositoryWeight() val repositoryPreferences = RepositoryPreferences( repoId = repoId, @@ -117,8 +127,8 @@ internal interface RepositoryDaoInt : RepositoryDao { @Transaction @VisibleForTesting - fun insert(repository: RepoV2): Long { - val repoId = insert(repository.toCoreRepository(version = 0)) + fun insertOrReplace(repository: RepoV2): Long { + val repoId = insertOrReplace(repository.toCoreRepository(version = 0)) insertRepositoryPreferences(repoId) insertRepoTables(repoId, repository) return repoId @@ -131,9 +141,15 @@ internal interface RepositoryDaoInt : RepositoryDao { } @Transaction - override fun replace(repoId: Long, repository: RepoV2, version: Int, certificate: String?) { - val newRepoId = insert(repository.toCoreRepository(repoId, version, certificate)) - require(newRepoId == repoId) { "New repoId $newRepoId did not match old $repoId" } + override 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 update(repoId: Long, repository: RepoV2, version: Int, certificate: String?) { + update(repository.toCoreRepository(repoId, version, certificate)) insertRepoTables(repoId, repository) } diff --git a/database/src/main/java/org/fdroid/index/v1/IndexV1Updater.kt b/database/src/main/java/org/fdroid/index/v1/IndexV1Updater.kt index f7e2ccafe..e8e947a09 100644 --- a/database/src/main/java/org/fdroid/index/v1/IndexV1Updater.kt +++ b/database/src/main/java/org/fdroid/index/v1/IndexV1Updater.kt @@ -65,9 +65,9 @@ public class IndexV1Updater( db.runInTransaction { val cert = verifier.getStreamAndVerify { inputStream -> updateListener?.onStartProcessing() // TODO maybe do more fine-grained reporting - val streamReceiver = DbV1StreamReceiver(db, compatibilityChecker) + val streamReceiver = DbV1StreamReceiver(db, compatibilityChecker, repoId) val streamProcessor = IndexV1StreamProcessor(streamReceiver, certificate) - streamProcessor.process(repoId, inputStream) + streamProcessor.process(inputStream) } // update certificate, if we didn't have any before if (certificate == null) {