[db] Calculate app compatibility in DB

and remove feature version as it app can't even use that.

Compatibility checking has been added to the DB layer as a post-processing step only because the UI wants to query for that on the app level (which would need all APKs).
This commit is contained in:
Torsten Grote
2022-04-06 14:19:07 -03:00
committed by Michael Pöhn
parent 3cb7538fc8
commit 4df60a42c8
15 changed files with 171 additions and 81 deletions

View File

@@ -25,7 +25,7 @@ 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), null) {
val indexProcessor = IndexV1StreamProcessor(DbV1StreamReceiver(db) { true }, null) {
val bytesRead = inputStream.byteCount
val bytesSinceLastCall = bytesRead - currentByteCount
if (bytesSinceLastCall > 0) {
@@ -84,10 +84,10 @@ class IndexV1InsertTest : DbTest() {
val localizedFileLists2 = localizedFileLists.count { it.repoId == 2L }
assertEquals(localizedFileLists1, localizedFileLists2)
appMetadata.filter { it.repoId ==2L }.forEach { m ->
appMetadata.filter { it.repoId == 2L }.forEach { m ->
val metadata1 = appDao.getAppMetadata(1, m.packageId)
val metadata2 = appDao.getAppMetadata(2, m.packageId)
assertEquals(metadata1, metadata2.copy(repoId = 1))
assertEquals(metadata1, metadata2.copy(repoId = 1, isCompatible = true))
val lFiles1 = appDao.getLocalizedFiles(1, m.packageId).toSet()
val lFiles2 = appDao.getLocalizedFiles(2, m.packageId)
@@ -111,7 +111,7 @@ class IndexV1InsertTest : DbTest() {
private fun insertV2ForComparison() {
val c = getApplicationContext<Context>()
val inputStream = CountingInputStream(c.resources.assets.open("index-v2.json"))
val indexProcessor = IndexStreamProcessor(DbStreamReceiver(db), null)
val indexProcessor = IndexStreamProcessor(DbStreamReceiver(db) { true }, null)
db.runInTransaction {
val repoId = db.getRepositoryDao().insertEmptyRepo("https://f-droid.org/repo")
inputStream.use { indexStream ->
@@ -124,7 +124,7 @@ class IndexV1InsertTest : DbTest() {
fun testExceptionWhileStreamingDoesNotSaveIntoDb() {
val c = getApplicationContext<Context>()
val cIn = CountingInputStream(c.resources.assets.open("index-v1.json"))
val indexProcessor = IndexStreamProcessor(DbStreamReceiver(db), null) {
val indexProcessor = IndexStreamProcessor(DbStreamReceiver(db) { true }, null) {
if (cIn.byteCount > 824096) throw SerializationException()
cIn.byteCount
}

View File

@@ -22,7 +22,7 @@ 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), null) {
val indexProcessor = IndexStreamProcessor(DbStreamReceiver(db) { true }, null) {
val bytesRead = inputStream.byteCount
val bytesSinceLastCall = bytesRead - currentByteCount
if (bytesSinceLastCall > 0) {
@@ -60,7 +60,7 @@ class IndexV2InsertTest : DbTest() {
fun testExceptionWhileStreamingDoesNotSaveIntoDb() {
val c = getApplicationContext<Context>()
val cIn = CountingInputStream(c.resources.assets.open("index-v2.json"))
val indexProcessor = IndexStreamProcessor(DbStreamReceiver(db), null) {
val indexProcessor = IndexStreamProcessor(DbStreamReceiver(db) { true }, null) {
if (cIn.byteCount > 824096) throw SerializationException()
cIn.byteCount
}

View File

@@ -8,6 +8,7 @@ import org.fdroid.database.test.TestUtils.getRandomString
import org.fdroid.database.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
@@ -64,7 +65,7 @@ class RepositoryTest : DbTest() {
val versionId = getRandomString()
appDao.insert(repoId, packageId, getRandomMetadataV2())
val packageVersion = getRandomPackageVersionV2()
versionDao.insert(repoId, packageId, versionId, packageVersion)
versionDao.insert(repoId, packageId, versionId, packageVersion, Random.nextBoolean())
assertEquals(1, repoDao.getRepositories().size)
assertEquals(1, appDao.getAppMetadata().size)

View File

@@ -29,7 +29,7 @@ class UpdateCheckerTest : DbTest() {
@Test
fun testGetUpdates() {
val inputStream = CountingInputStream(context.resources.assets.open("index-v1.json"))
val indexProcessor = IndexV1StreamProcessor(DbV1StreamReceiver(db), null)
val indexProcessor = IndexV1StreamProcessor(DbV1StreamReceiver(db) { true }, null)
db.runInTransaction {
val repoId = db.getRepositoryDao().insertEmptyRepo("https://f-droid.org/repo")

View File

@@ -10,6 +10,7 @@ import org.fdroid.database.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.fail
@@ -27,21 +28,25 @@ class VersionTest : DbTest() {
val repoId = repoDao.insert(getRandomRepo())
appDao.insert(repoId, packageId, getRandomMetadataV2())
val packageVersion = getRandomPackageVersionV2()
versionDao.insert(repoId, packageId, versionId, packageVersion)
val isCompatible = Random.nextBoolean()
versionDao.insert(repoId, packageId, versionId, packageVersion, isCompatible)
val appVersions = versionDao.getAppVersions(repoId, packageId)
assertEquals(1, appVersions.size)
val appVersion = appVersions[0]
assertEquals(versionId, appVersion.version.versionId)
assertEquals(packageVersion.toVersion(repoId, packageId, versionId), appVersion.version)
val version = packageVersion.toVersion(repoId, packageId, versionId, isCompatible)
assertEquals(version, appVersion.version)
val manifest = packageVersion.manifest
assertEquals(manifest.usesPermission.toSet(), appVersion.usesPermission?.toSet())
assertEquals(manifest.usesPermissionSdk23.toSet(), appVersion.usesPermissionSdk23?.toSet())
assertEquals(manifest.features.toSet(), appVersion.features?.toSet())
assertEquals(
manifest.features.map { it.name }.toSet(),
appVersion.version.manifest.features?.toSet()
)
val versionedStrings = versionDao.getVersionedStrings(repoId, packageId)
val expectedSize =
manifest.usesPermission.size + manifest.usesPermissionSdk23.size + manifest.features.size
val expectedSize = manifest.usesPermission.size + manifest.usesPermissionSdk23.size
assertEquals(expectedSize, versionedStrings.size)
versionDao.deleteAppVersion(repoId, packageId, versionId)
@@ -55,11 +60,13 @@ class VersionTest : DbTest() {
val repoId = repoDao.insert(getRandomRepo())
appDao.insert(repoId, packageId, getRandomMetadataV2())
val packageVersion1 = getRandomPackageVersionV2()
val version1 = getRandomString()
versionDao.insert(repoId, packageId, version1, packageVersion1)
val packageVersion2 = getRandomPackageVersionV2()
val version1 = getRandomString()
val version2 = getRandomString()
versionDao.insert(repoId, packageId, version2, packageVersion2)
val isCompatible1 = Random.nextBoolean()
val isCompatible2 = Random.nextBoolean()
versionDao.insert(repoId, packageId, version1, packageVersion1, isCompatible1)
versionDao.insert(repoId, packageId, version2, packageVersion2, isCompatible2)
// get app versions from DB and assign them correctly
val appVersions = versionDao.getAppVersions(packageId).getOrAwaitValue() ?: fail()
@@ -72,19 +79,27 @@ class VersionTest : DbTest() {
} else appVersions[1]
// check first version matches
assertEquals(packageVersion1.toVersion(repoId, packageId, version1), appVersion.version)
val exVersion1 = packageVersion1.toVersion(repoId, packageId, version1, isCompatible1)
assertEquals(exVersion1, appVersion.version)
val manifest = packageVersion1.manifest
assertEquals(manifest.usesPermission.toSet(), appVersion.usesPermission?.toSet())
assertEquals(manifest.usesPermissionSdk23.toSet(), appVersion.usesPermissionSdk23?.toSet())
assertEquals(manifest.features.toSet(), appVersion.features?.toSet())
assertEquals(
manifest.features.map { it.name }.toSet(),
appVersion.version.manifest.features?.toSet()
)
// check second version matches
assertEquals(packageVersion2.toVersion(repoId, packageId, version2), appVersion2.version)
val exVersion2 = packageVersion2.toVersion(repoId, packageId, version2, isCompatible2)
assertEquals(exVersion2, appVersion2.version)
val manifest2 = packageVersion2.manifest
assertEquals(manifest2.usesPermission.toSet(), appVersion2.usesPermission?.toSet())
assertEquals(manifest2.usesPermissionSdk23.toSet(),
appVersion2.usesPermissionSdk23?.toSet())
assertEquals(manifest2.features.toSet(), appVersion2.features?.toSet())
assertEquals(
manifest.features.map { it.name }.toSet(),
appVersion.version.manifest.features?.toSet()
)
// delete app and check that all associated data also gets deleted
appDao.deleteAppMetadata(repoId, packageId)

View File

@@ -47,9 +47,7 @@ internal object TestVersionUtils {
PermissionV2(getRandomString(), Random.nextInt().orNull())
},
nativeCode = getRandomList(Random.nextInt(0, 4)) { getRandomString() },
features = getRandomList {
FeatureV2(getRandomString(), Random.nextInt().orNull())
},
features = getRandomList { FeatureV2(getRandomString()) },
)
}

View File

@@ -35,6 +35,8 @@ data class AppMetadata(
val name: LocalizedTextV2? = null,
val summary: LocalizedTextV2? = null,
val description: LocalizedTextV2? = null,
val localizedName: String? = null,
val localizedSummary: String? = null,
val webSite: String? = null,
val changelog: String? = null,
val license: String? = null,
@@ -46,9 +48,20 @@ data class AppMetadata(
@Embedded(prefix = "author_") val author: Author? = Author(),
@Embedded(prefix = "donation_") val donation: Donation? = Donation(),
val categories: List<String>? = null,
/**
* Whether the app is compatible with the current device.
* This value will be computed and is always false until that happened.
* So to always get correct data, this MUST happen within the same transaction
* that adds the [AppMetadata].
*/
val isCompatible: Boolean,
)
fun MetadataV2.toAppMetadata(repoId: Long, packageId: String) = AppMetadata(
fun MetadataV2.toAppMetadata(
repoId: Long,
packageId: String,
isCompatible: Boolean = false,
) = AppMetadata(
repoId = repoId,
packageId = packageId,
added = added,
@@ -67,6 +80,7 @@ fun MetadataV2.toAppMetadata(repoId: Long, packageId: String) = AppMetadata(
author = if (author?.isNull == true) null else author,
donation = if (donation?.isNull == true) null else donation,
categories = categories,
isCompatible = isCompatible,
)
data class App(
@@ -143,8 +157,7 @@ public data class AppListItem @JvmOverloads constructor(
/**
* If true, this this app has at least one version that is compatible with this device.
*/
@Ignore // TODO actually get this from the DB (probably needs post-processing).
public val isCompatible: Boolean = true,
public val isCompatible: Boolean,
/**
* The name of the installed version, null if this app is not installed.
*/

View File

@@ -48,7 +48,7 @@ internal interface AppDaoInt : AppDao {
@Transaction
override fun insert(repoId: Long, packageId: String, app: MetadataV2) {
insert(app.toAppMetadata(repoId, packageId))
insert(app.toAppMetadata(repoId, packageId, false))
app.icon.insert(repoId, packageId, "icon")
app.featureGraphic.insert(repoId, packageId, "featureGraphic")
app.promoGraphic.insert(repoId, packageId, "promoGraphic")
@@ -90,6 +90,20 @@ internal interface AppDaoInt : AppDao {
@Query("UPDATE AppMetadata SET preferredSigner = :preferredSigner WHERE repoId = :repoId AND packageId = :packageId")
fun updatePreferredSigner(repoId: Long, packageId: String, preferredSigner: String?)
/**
* Updates the [AppMetadata.isCompatible] flag
* based on whether at least one [AppVersion] is compatible.
* This needs to run within the transaction that adds [AppMetadata] to the DB.
* Otherwise the compatibility is wrong.
*/
@Query("""UPDATE AppMetadata
SET isCompatible = (
SELECT TOTAL(isCompatible) > 0 FROM Version
WHERE repoId = :repoId AND AppMetadata.packageId = Version.packageId
)
WHERE repoId = :repoId""")
fun updateCompatibility(repoId: Long)
override fun getApp(packageId: String): LiveData<App?> {
return getRepoIdForPackage(packageId).distinctUntilChanged().switchMap { repoId ->
if (repoId == null) MutableLiveData(null)
@@ -227,7 +241,8 @@ internal interface AppDaoInt : AppDao {
}
@Transaction
@Query("""SELECT repoId, packageId, app.name, summary, version.antiFeatures
@Query("""
SELECT repoId, packageId, app.name, summary, version.antiFeatures, app.isCompatible
FROM AppMetadata AS app
JOIN Version AS version USING (repoId, packageId)
JOIN RepositoryPreferences AS pref USING (repoId)
@@ -245,7 +260,8 @@ internal interface AppDaoInt : AppDao {
// TODO maybe it makes sense to split categories into their own table for this?
@Transaction
@Query("""SELECT repoId, packageId, app.name, summary, version.antiFeatures
@Query("""
SELECT repoId, packageId, app.name, summary, version.antiFeatures, app.isCompatible
FROM AppMetadata AS app
JOIN Version AS version USING (repoId, packageId)
JOIN RepositoryPreferences AS pref USING (repoId)
@@ -256,7 +272,7 @@ internal interface AppDaoInt : AppDao {
@Transaction
@SuppressWarnings(CURSOR_MISMATCH) // no anti-features needed here
@Query("""SELECT repoId, packageId, app.name, summary
@Query("""SELECT repoId, packageId, app.name, summary, app.isCompatible
FROM AppMetadata AS app
JOIN RepositoryPreferences AS pref USING (repoId)
WHERE pref.enabled = 1 AND packageId IN (:packageNames)

View File

@@ -1,11 +1,13 @@
package org.fdroid.database
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 {
override fun receive(repoId: Long, repo: RepoV2, certificate: String?) {
@@ -14,7 +16,13 @@ internal class DbStreamReceiver(
override fun receive(repoId: Long, packageId: String, p: PackageV2) {
db.getAppDao().insert(repoId, packageId, p.metadata)
db.getVersionDao().insert(repoId, packageId, p.versions)
db.getVersionDao().insert(repoId, packageId, p.versions) {
compatibilityChecker.isCompatible(it.manifest)
}
}
override fun onStreamEnded(repoId: Long) {
db.getAppDao().updateCompatibility(repoId)
}
}

View File

@@ -1,5 +1,6 @@
package org.fdroid.database
import org.fdroid.CompatibilityChecker
import org.fdroid.index.v1.IndexV1StreamReceiver
import org.fdroid.index.v2.AntiFeatureV2
import org.fdroid.index.v2.CategoryV2
@@ -10,6 +11,7 @@ import org.fdroid.index.v2.RepoV2
internal class DbV1StreamReceiver(
private val db: FDroidDatabaseInt,
private val compatibilityChecker: CompatibilityChecker,
) : IndexV1StreamReceiver {
override fun receive(repoId: Long, repo: RepoV2, certificate: String?) {
@@ -21,7 +23,9 @@ internal class DbV1StreamReceiver(
}
override fun receive(repoId: Long, packageId: String, v: Map<String, PackageVersionV2>) {
db.getVersionDao().insert(repoId, packageId, v)
db.getVersionDao().insert(repoId, packageId, v) {
compatibilityChecker.isCompatible(it.manifest)
}
}
override fun updateRepo(
@@ -34,6 +38,8 @@ internal class DbV1StreamReceiver(
repoDao.insertAntiFeatures(antiFeatures.toRepoAntiFeatures(repoId))
repoDao.insertCategories(categories.toRepoCategories(repoId))
repoDao.insertReleaseChannels(releaseChannels.toRepoReleaseChannel(repoId))
db.getAppDao().updateCompatibility(repoId)
}
override fun updateAppMetadata(repoId: Long, packageId: String, preferredSigner: String?) {

View File

@@ -224,7 +224,7 @@ data class RepositoryPreferences(
@PrimaryKey internal val repoId: Long,
val weight: Int,
val enabled: Boolean = true,
val lastUpdated: Long? = System.currentTimeMillis(), // TODO set this after repo updates
val lastUpdated: Long? = System.currentTimeMillis(),
val lastETag: String? = null,
val userMirrors: List<String>? = null,
val disabledMirrors: List<String>? = null,

View File

@@ -5,6 +5,7 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.GET_SIGNATURES
import android.os.Build
import org.fdroid.CompatibilityCheckerImpl
import org.fdroid.index.IndexUtils
public class UpdateChecker(
@@ -14,9 +15,16 @@ public class UpdateChecker(
private val appDao = db.getAppDao() as AppDaoInt
private val versionDao = db.getVersionDao() as VersionDaoInt
private val compatibilityChecker = CompatibilityCheckerImpl(packageManager)
fun getUpdatableApps(): List<UpdatableApp> {
/**
* Returns a list of apps that can be updated.
* @param releaseChannels optional list of release channels to consider on top of stable.
* If this is null or empty, only versions without channel (stable) will be considered.
*/
fun getUpdatableApps(releaseChannels: List<String>? = null): List<UpdatableApp> {
val updatableApps = ArrayList<UpdatableApp>()
@Suppress("DEPRECATION") // we'll use this as long as it works, new one was broken
val installedPackages = packageManager.getInstalledPackages(GET_SIGNATURES)
val packageNames = installedPackages.map { it.packageName }
@@ -27,7 +35,7 @@ public class UpdateChecker(
}
installedPackages.iterator().forEach { packageInfo ->
val versions = versionsByPackage[packageInfo.packageName] ?: return@forEach // continue
val version = getVersion(versions, packageInfo)
val version = getVersion(versions, packageInfo, releaseChannels)
if (version != null) {
val versionCode = packageInfo.getVersionCode()
val app = getUpdatableApp(version, versionCode)
@@ -37,8 +45,14 @@ public class UpdateChecker(
return updatableApps
}
/**
* Returns an [AppVersion] for the given [packageName] that is an update
* or null if there is none.
* @param releaseChannels optional list of release channels to consider on top of stable.
* If this is null or empty, only versions without channel (stable) will be considered.
*/
@SuppressLint("PackageManagerGetSignatures")
fun getUpdate(packageName: String): AppVersion? {
fun getUpdate(packageName: String, releaseChannels: List<String>? = null): AppVersion? {
val versions = versionDao.getVersions(listOf(packageName))
if (versions.isEmpty()) return null
val packageInfo = try {
@@ -47,7 +61,7 @@ public class UpdateChecker(
} catch (e: PackageManager.NameNotFoundException) {
null
}
val version = getVersion(versions, packageInfo) ?: return null
val version = getVersion(versions, packageInfo, releaseChannels) ?: return null
val versionedStrings = versionDao.getVersionedStrings(
repoId = version.repoId,
packageId = version.packageId,
@@ -56,7 +70,11 @@ public class UpdateChecker(
return version.toAppVersion(versionedStrings)
}
private fun getVersion(versions: List<Version>, packageInfo: PackageInfo?): Version? {
private fun getVersion(
versions: List<Version>,
packageInfo: PackageInfo?,
releaseChannels: List<String>?,
): Version? {
val versionCode = packageInfo?.getVersionCode() ?: 0
// the below is rather expensive, so we only do that when there's update candidates
// TODO handle signingInfo.signingCertificateHistory as well
@@ -69,8 +87,15 @@ public class UpdateChecker(
versions.iterator().forEach versions@{ version ->
// if version code is not higher than installed skip package as list is sorted
if (version.manifest.versionCode <= versionCode) return null
// not considering beta versions for now
if (!version.releaseChannels.isNullOrEmpty()) return@versions
// check release channels if they are not empty
if (!version.releaseChannels.isNullOrEmpty()) {
// if release channels are not empty (stable) don't consider this version
if (releaseChannels == null) return@versions
// don't consider version with non-matching release channel
if (releaseChannels.intersect(version.releaseChannels).isEmpty()) return@versions
}
// skip incompatible versions
if (!compatibilityChecker.isCompatible(version.manifest)) return@versions
val canInstall = if (packageInfo == null) {
true // take first one with highest version code and repo weight
} else {

View File

@@ -4,7 +4,6 @@ import androidx.core.os.LocaleListCompat
import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.ForeignKey
import org.fdroid.database.VersionedStringType.FEATURE
import org.fdroid.database.VersionedStringType.PERMISSION
import org.fdroid.database.VersionedStringType.PERMISSION_SDK_23
import org.fdroid.index.v2.FeatureV2
@@ -37,16 +36,21 @@ data class Version(
val releaseChannels: List<String>? = emptyList(),
val antiFeatures: Map<String, LocalizedTextV2>? = null,
val whatsNew: LocalizedTextV2? = null,
val isCompatible: Boolean,
) {
fun toAppVersion(versionedStrings: List<VersionedString>) = AppVersion(
version = this,
usesPermission = versionedStrings.getPermissions(this),
usesPermissionSdk23 = versionedStrings.getPermissionsSdk23(this),
features = versionedStrings.getFeatures(this),
)
}
fun PackageVersionV2.toVersion(repoId: Long, packageId: String, versionId: String) = Version(
fun PackageVersionV2.toVersion(
repoId: Long,
packageId: String,
versionId: String,
isCompatible: Boolean,
) = Version(
repoId = repoId,
packageId = packageId,
versionId = versionId,
@@ -57,16 +61,16 @@ fun PackageVersionV2.toVersion(repoId: Long, packageId: String, versionId: Strin
releaseChannels = releaseChannels,
antiFeatures = antiFeatures,
whatsNew = whatsNew,
isCompatible = isCompatible,
)
data class AppVersion(
val version: Version,
val usesPermission: List<PermissionV2>? = null,
val usesPermissionSdk23: List<PermissionV2>? = null,
val features: List<FeatureV2>? = null,
) {
val packageId get() = version.packageId
val featureNames get() = features?.map { it.name }?.toTypedArray() ?: emptyArray()
val featureNames get() = version.manifest.features?.toTypedArray() ?: emptyArray()
val nativeCode get() = version.manifest.nativecode?.toTypedArray() ?: emptyArray()
val antiFeatureNames: Array<String>
get() {
@@ -83,7 +87,18 @@ data class AppManifest(
val maxSdkVersion: Int? = null,
@Embedded(prefix = "signer_") val signer: SignatureV2? = null,
val nativecode: List<String>? = emptyList(),
)
val features: List<String>? = emptyList(),
) {
internal fun toManifestV2(): ManifestV2 = ManifestV2(
versionName = versionName,
versionCode = versionCode,
usesSdk = usesSdk,
maxSdkVersion = maxSdkVersion,
signer = signer,
nativeCode = nativecode ?: emptyList(),
features = features?.map { FeatureV2(it) } ?: emptyList(),
)
}
fun ManifestV2.toManifest() = AppManifest(
versionName = versionName,
@@ -92,12 +107,12 @@ fun ManifestV2.toManifest() = AppManifest(
maxSdkVersion = maxSdkVersion,
signer = signer,
nativecode = nativeCode,
features = features.map { it.name },
)
enum class VersionedStringType {
PERMISSION,
PERMISSION_SDK_23,
FEATURE,
}
@Entity(
@@ -132,21 +147,9 @@ fun List<PermissionV2>.toVersionedString(
)
}
fun List<FeatureV2>.toVersionedString(version: Version) = map { feature ->
VersionedString(
repoId = version.repoId,
packageId = version.packageId,
versionId = version.versionId,
type = FEATURE,
name = feature.name,
version = feature.version,
)
}
fun ManifestV2.getVersionedStrings(version: Version): List<VersionedString> {
return usesPermission.toVersionedString(version, PERMISSION) +
usesPermissionSdk23.toVersionedString(version, PERMISSION_SDK_23) +
features.toVersionedString(version)
usesPermissionSdk23.toVersionedString(version, PERMISSION_SDK_23)
}
fun List<VersionedString>.getPermissions(version: Version) = mapNotNull { v ->
@@ -167,15 +170,6 @@ fun List<VersionedString>.getPermissionsSdk23(version: Version) = mapNotNull { v
}
}
fun List<VersionedString>.getFeatures(version: Version) = mapNotNull { v ->
v.map(version, FEATURE) {
FeatureV2(
name = v.name,
version = v.version,
)
}
}
private fun <T> VersionedString.map(
v: Version,
wantedType: VersionedStringType,

View File

@@ -15,8 +15,13 @@ import org.fdroid.database.FDroidDatabaseHolder.dispatcher
import org.fdroid.index.v2.PackageVersionV2
public interface VersionDao {
fun insert(repoId: Long, packageId: String, packageVersions: Map<String, PackageVersionV2>)
fun insert(repoId: Long, packageId: String, versionId: String, packageVersion: PackageVersionV2)
fun insert(
repoId: Long,
packageId: String,
packageVersions: Map<String, PackageVersionV2>,
checkIfCompatible: (PackageVersionV2) -> Boolean,
)
fun getAppVersions(packageId: String): LiveData<List<AppVersion>>
fun getAppVersions(repoId: Long, packageId: String): List<AppVersion>
}
@@ -29,21 +34,24 @@ internal interface VersionDaoInt : VersionDao {
repoId: Long,
packageId: String,
packageVersions: Map<String, PackageVersionV2>,
checkIfCompatible: (PackageVersionV2) -> Boolean,
) {
// TODO maybe the number of queries here can be reduced
packageVersions.entries.forEach { (versionId, packageVersion) ->
insert(repoId, packageId, versionId, packageVersion)
packageVersions.entries.iterator().forEach { (versionId, packageVersion) ->
val isCompatible = checkIfCompatible(packageVersion)
insert(repoId, packageId, versionId, packageVersion, isCompatible)
}
}
@Transaction
override fun insert(
fun insert(
repoId: Long,
packageId: String,
versionId: String,
packageVersion: PackageVersionV2,
isCompatible: Boolean,
) {
val version = packageVersion.toVersion(repoId, packageId, versionId)
val version = packageVersion.toVersion(repoId, packageId, versionId, isCompatible)
insert(version)
insert(packageVersion.manifest.getVersionedStrings(version))
}
@@ -68,8 +76,8 @@ internal interface VersionDaoInt : VersionDao {
@Transaction
override fun getAppVersions(repoId: Long, packageId: String): List<AppVersion> {
val versionedStrings = getVersionedStrings(repoId, packageId)
return getVersions(repoId, packageId).map {
version -> version.toAppVersion(versionedStrings)
return getVersions(repoId, packageId).map { version ->
version.toAppVersion(versionedStrings)
}
}
@@ -100,7 +108,11 @@ internal interface VersionDaoInt : VersionDao {
fun getVersionedStrings(repoId: Long, packageId: String): List<VersionedString>
@Query("SELECT * FROM VersionedString WHERE repoId = :repoId AND packageId = :packageId AND versionId = :versionId")
fun getVersionedStrings(repoId: Long, packageId: String, versionId: String): List<VersionedString>
fun getVersionedStrings(
repoId: Long,
packageId: String,
versionId: String,
): List<VersionedString>
@VisibleForTesting
@Query("DELETE FROM Version WHERE repoId = :repoId AND packageId = :packageId AND versionId = :versionId")

View File

@@ -1,6 +1,7 @@
package org.fdroid.index.v1
import android.content.Context
import org.fdroid.CompatibilityChecker
import org.fdroid.database.DbV1StreamReceiver
import org.fdroid.database.FDroidDatabase
import org.fdroid.database.FDroidDatabaseInt
@@ -15,6 +16,7 @@ public class IndexV1Updater(
private val context: Context,
database: FDroidDatabase,
private val downloaderFactory: DownloaderFactory,
private val compatibilityChecker: CompatibilityChecker,
) {
private val db: FDroidDatabaseInt = database as FDroidDatabaseInt
@@ -63,8 +65,8 @@ public class IndexV1Updater(
db.runInTransaction {
val cert = verifier.getStreamAndVerify { inputStream ->
updateListener?.onStartProcessing() // TODO maybe do more fine-grained reporting
val streamProcessor =
IndexV1StreamProcessor(DbV1StreamReceiver(db), certificate)
val streamReceiver = DbV1StreamReceiver(db, compatibilityChecker)
val streamProcessor = IndexV1StreamProcessor(streamReceiver, certificate)
streamProcessor.process(repoId, inputStream)
}
// update certificate, if we didn't have any before