mirror of
https://github.com/f-droid/fdroidclient.git
synced 2026-04-20 06:47:06 -04:00
[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:
committed by
Michael Pöhn
parent
3cb7538fc8
commit
4df60a42c8
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()) },
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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?) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user