mirror of
https://github.com/f-droid/fdroidclient.git
synced 2026-05-24 00:14:43 -04:00
[db] Implement interfaces from index library
so we can use UpdateChecker and CompatibilityChecker with our DB classes
This commit is contained in:
committed by
Michael Pöhn
parent
55d78fab01
commit
890dc45718
@@ -10,15 +10,15 @@ import kotlin.time.ExperimentalTime
|
||||
import kotlin.time.measureTime
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
internal class UpdateCheckerTest : DbTest() {
|
||||
internal class DbUpdateCheckerTest : DbTest() {
|
||||
|
||||
private lateinit var updateChecker: UpdateChecker
|
||||
private lateinit var updateChecker: DbUpdateChecker
|
||||
|
||||
@Before
|
||||
override fun createDb() {
|
||||
super.createDb()
|
||||
// TODO mock packageManager and maybe move to unit tests
|
||||
updateChecker = UpdateChecker(db, context.packageManager)
|
||||
updateChecker = DbUpdateChecker(db, context.packageManager)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -464,7 +464,7 @@ internal interface AppDaoInt : AppDao {
|
||||
override fun getNumberOfAppsInCategory(category: String): Int
|
||||
|
||||
/**
|
||||
* Used by [UpdateChecker] to get specific apps with available updates.
|
||||
* Used by [DbUpdateChecker] to get specific apps with available updates.
|
||||
*/
|
||||
@Transaction
|
||||
@SuppressWarnings(CURSOR_MISMATCH) // no anti-features needed here
|
||||
|
||||
@@ -2,18 +2,19 @@ package org.fdroid.database
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import org.fdroid.PackagePreference
|
||||
|
||||
@Entity
|
||||
public data class AppPrefs(
|
||||
@PrimaryKey
|
||||
val packageId: String,
|
||||
val ignoreVersionCodeUpdate: Long = 0,
|
||||
override val ignoreVersionCodeUpdate: Long = 0,
|
||||
// This is named like this, because it hit a Room bug when joining with Version table
|
||||
// which had exactly the same field.
|
||||
internal val appPrefReleaseChannels: List<String>? = null,
|
||||
) {
|
||||
) : PackagePreference {
|
||||
public val ignoreAllUpdates: Boolean get() = ignoreVersionCodeUpdate == Long.MAX_VALUE
|
||||
public val releaseChannels: List<String> get() = appPrefReleaseChannels ?: emptyList()
|
||||
public override val releaseChannels: List<String> get() = appPrefReleaseChannels ?: emptyList()
|
||||
public fun shouldIgnoreUpdate(versionCode: Long): Boolean =
|
||||
ignoreVersionCodeUpdate >= versionCode
|
||||
|
||||
|
||||
@@ -6,9 +6,10 @@ 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
|
||||
import org.fdroid.PackagePreference
|
||||
import org.fdroid.UpdateChecker
|
||||
|
||||
public class UpdateChecker(
|
||||
public class DbUpdateChecker(
|
||||
db: FDroidDatabase,
|
||||
private val packageManager: PackageManager,
|
||||
) {
|
||||
@@ -17,6 +18,7 @@ public class UpdateChecker(
|
||||
private val versionDao = db.getVersionDao() as VersionDaoInt
|
||||
private val appPrefsDao = db.getAppPrefsDao() as AppPrefsDaoInt
|
||||
private val compatibilityChecker = CompatibilityCheckerImpl(packageManager)
|
||||
private val updateChecker = UpdateChecker(compatibilityChecker)
|
||||
|
||||
/**
|
||||
* Returns a list of apps that can be updated.
|
||||
@@ -37,7 +39,7 @@ public class UpdateChecker(
|
||||
installedPackages.iterator().forEach { packageInfo ->
|
||||
val packageName = packageInfo.packageName
|
||||
val versions = versionsByPackage[packageName] ?: return@forEach // continue
|
||||
val version = getVersion(versions, packageName, packageInfo, releaseChannels)
|
||||
val version = getVersion(versions, packageName, packageInfo, null, releaseChannels)
|
||||
if (version != null) {
|
||||
val versionCode = packageInfo.getVersionCode()
|
||||
val app = getUpdatableApp(version, versionCode)
|
||||
@@ -48,13 +50,17 @@ public class UpdateChecker(
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an [AppVersion] for the given [packageName] that is an update
|
||||
* Returns an [AppVersion] for the given [packageName] that is an update or new install
|
||||
* 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")
|
||||
public fun getUpdate(packageName: String, releaseChannels: List<String>? = null): AppVersion? {
|
||||
public fun getSuggestedVersion(
|
||||
packageName: String,
|
||||
preferredSigner: String? = null,
|
||||
releaseChannels: List<String>? = null,
|
||||
): AppVersion? {
|
||||
val versions = versionDao.getVersions(listOf(packageName))
|
||||
if (versions.isEmpty()) return null
|
||||
val packageInfo = try {
|
||||
@@ -63,7 +69,8 @@ public class UpdateChecker(
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
null
|
||||
}
|
||||
val version = getVersion(versions, packageName, packageInfo, releaseChannels) ?: return null
|
||||
val version = getVersion(versions, packageName, packageInfo, preferredSigner,
|
||||
releaseChannels) ?: return null
|
||||
val versionedStrings = versionDao.getVersionedStrings(
|
||||
repoId = version.repoId,
|
||||
packageId = version.packageId,
|
||||
@@ -76,50 +83,27 @@ public class UpdateChecker(
|
||||
versions: List<Version>,
|
||||
packageName: String,
|
||||
packageInfo: PackageInfo?,
|
||||
preferredSigner: String?,
|
||||
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
|
||||
@Suppress("DEPRECATION")
|
||||
val signatures by lazy {
|
||||
packageInfo?.signatures?.map {
|
||||
IndexUtils.getPackageSignature(it.toByteArray())
|
||||
}?.toSet()
|
||||
val preferencesGetter: (() -> PackagePreference?) = {
|
||||
appPrefsDao.getAppPrefsOrNull(packageName)
|
||||
}
|
||||
val appPrefs by lazy { appPrefsDao.getAppPrefsOrNull(packageName) }
|
||||
versions.iterator().forEach versions@{ version ->
|
||||
// if the installed version has a known vulnerability, we return it as well
|
||||
if (version.manifest.versionCode == versionCode && version.hasKnownVulnerability) {
|
||||
return version
|
||||
}
|
||||
// if version code is not higher than installed skip package as list is sorted
|
||||
if (version.manifest.versionCode <= versionCode) return null
|
||||
// skip incompatible versions
|
||||
if (!compatibilityChecker.isCompatible(version.manifest.toManifestV2())) return@versions
|
||||
// only check release channels if they are not empty
|
||||
if (!version.releaseChannels.isNullOrEmpty()) {
|
||||
// add release channels from AppPrefs into the ones we allow
|
||||
val channels = releaseChannels?.toMutableSet() ?: LinkedHashSet()
|
||||
if (!appPrefs?.releaseChannels.isNullOrEmpty()) {
|
||||
channels.addAll(appPrefs!!.releaseChannels)
|
||||
}
|
||||
// if allowed releases channels are empty (only stable) don't consider this version
|
||||
if (channels.isEmpty()) return@versions
|
||||
// don't consider version with non-matching release channel
|
||||
if (channels.intersect(version.releaseChannels).isEmpty()) return@versions
|
||||
}
|
||||
val canInstall = if (packageInfo == null) {
|
||||
true // take first one with highest version code and repo weight
|
||||
} else {
|
||||
// TODO also support AppPrefs with ignoring updates
|
||||
val versionSignatures = version.manifest.signer?.sha256?.toSet()
|
||||
signatures == versionSignatures
|
||||
}
|
||||
// no need to see other versions, we got the highest version code per sorting
|
||||
if (canInstall) return version
|
||||
return if (packageInfo == null) {
|
||||
updateChecker.getSuggestedVersion(
|
||||
versions = versions,
|
||||
preferredSigner = preferredSigner,
|
||||
releaseChannels = releaseChannels,
|
||||
preferencesGetter = preferencesGetter,
|
||||
)
|
||||
} else {
|
||||
updateChecker.getUpdate(
|
||||
versions = versions,
|
||||
packageInfo = packageInfo,
|
||||
releaseChannels = releaseChannels,
|
||||
preferencesGetter = preferencesGetter,
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getUpdatableApp(version: Version, installedVersionCode: Long): UpdatableApp? {
|
||||
@@ -6,18 +6,18 @@ import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import org.fdroid.database.VersionedStringType.PERMISSION
|
||||
import org.fdroid.database.VersionedStringType.PERMISSION_SDK_23
|
||||
import org.fdroid.index.v2.FeatureV2
|
||||
import org.fdroid.index.v2.ANTI_FEATURE_KNOWN_VULNERABILITY
|
||||
import org.fdroid.index.v2.FileV1
|
||||
import org.fdroid.index.v2.FileV2
|
||||
import org.fdroid.index.v2.LocalizedTextV2
|
||||
import org.fdroid.index.v2.ManifestV2
|
||||
import org.fdroid.index.v2.PackageManifest
|
||||
import org.fdroid.index.v2.PackageVersion
|
||||
import org.fdroid.index.v2.PackageVersionV2
|
||||
import org.fdroid.index.v2.PermissionV2
|
||||
import org.fdroid.index.v2.SignatureV2
|
||||
import org.fdroid.index.v2.SignerV2
|
||||
import org.fdroid.index.v2.UsesSdkV2
|
||||
|
||||
private const val ANTI_FEATURE_KNOWN_VULNERABILITY = "KnownVuln"
|
||||
|
||||
@Entity(
|
||||
primaryKeys = ["repoId", "packageId", "versionId"],
|
||||
foreignKeys = [ForeignKey(
|
||||
@@ -35,12 +35,15 @@ public data class Version(
|
||||
@Embedded(prefix = "file_") val file: FileV1,
|
||||
@Embedded(prefix = "src_") val src: FileV2? = null,
|
||||
@Embedded(prefix = "manifest_") val manifest: AppManifest,
|
||||
val releaseChannels: List<String>? = emptyList(),
|
||||
override val releaseChannels: List<String>? = emptyList(),
|
||||
val antiFeatures: Map<String, LocalizedTextV2>? = null,
|
||||
val whatsNew: LocalizedTextV2? = null,
|
||||
val isCompatible: Boolean,
|
||||
) {
|
||||
val hasKnownVulnerability: Boolean
|
||||
) : PackageVersion {
|
||||
override val versionCode: Long get() = manifest.versionCode
|
||||
override val signer: SignerV2? get() = manifest.signer
|
||||
override val packageManifest: PackageManifest get() = manifest
|
||||
override val hasKnownVulnerability: Boolean
|
||||
get() = antiFeatures?.contains(ANTI_FEATURE_KNOWN_VULNERABILITY) == true
|
||||
|
||||
internal fun toAppVersion(versionedStrings: List<VersionedString>): AppVersion = AppVersion(
|
||||
@@ -97,20 +100,13 @@ public data class AppManifest(
|
||||
val versionName: String,
|
||||
val versionCode: Long,
|
||||
@Embedded(prefix = "usesSdk_") val usesSdk: UsesSdkV2? = null,
|
||||
val maxSdkVersion: Int? = null,
|
||||
@Embedded(prefix = "signer_") val signer: SignatureV2? = null,
|
||||
val nativecode: List<String>? = emptyList(),
|
||||
override val maxSdkVersion: Int? = null,
|
||||
@Embedded(prefix = "signer_") val signer: SignerV2? = null,
|
||||
override 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(),
|
||||
)
|
||||
) : PackageManifest {
|
||||
override val minSdkVersion: Int? get() = usesSdk?.minSdkVersion
|
||||
override val featureNames: List<String>? get() = features
|
||||
}
|
||||
|
||||
internal fun ManifestV2.toManifest() = AppManifest(
|
||||
|
||||
@@ -19,7 +19,7 @@ import org.fdroid.database.FDroidDatabaseHolder.dispatcher
|
||||
import org.fdroid.database.VersionedStringType.PERMISSION
|
||||
import org.fdroid.database.VersionedStringType.PERMISSION_SDK_23
|
||||
import org.fdroid.index.IndexParser.json
|
||||
import org.fdroid.index.v2.ManifestV2
|
||||
import org.fdroid.index.v2.PackageManifest
|
||||
import org.fdroid.index.v2.PackageVersionV2
|
||||
import org.fdroid.index.v2.PermissionV2
|
||||
import org.fdroid.index.v2.ReflectionDiffer
|
||||
@@ -88,7 +88,7 @@ internal interface VersionDaoInt : VersionDao {
|
||||
repoId: Long,
|
||||
packageId: String,
|
||||
versionsDiffMap: Map<String, JsonObject?>?,
|
||||
checkIfCompatible: (ManifestV2) -> Boolean,
|
||||
checkIfCompatible: (PackageManifest) -> Boolean,
|
||||
) {
|
||||
if (versionsDiffMap == null) { // no more versions, delete all
|
||||
deleteAppVersion(repoId, packageId)
|
||||
@@ -100,7 +100,7 @@ internal interface VersionDaoInt : VersionDao {
|
||||
if (version == null) { // new version, parse normally
|
||||
val packageVersionV2: PackageVersionV2 =
|
||||
json.decodeFromJsonElement(jsonObject)
|
||||
val isCompatible = checkIfCompatible(packageVersionV2.manifest)
|
||||
val isCompatible = checkIfCompatible(packageVersionV2.packageManifest)
|
||||
insert(repoId, packageId, versionId, packageVersionV2, isCompatible)
|
||||
} else { // diff against existing version
|
||||
diffVersion(version, jsonObject, checkIfCompatible)
|
||||
@@ -112,7 +112,7 @@ internal interface VersionDaoInt : VersionDao {
|
||||
private fun diffVersion(
|
||||
version: Version,
|
||||
jsonObject: JsonObject,
|
||||
checkIfCompatible: (ManifestV2) -> Boolean,
|
||||
checkIfCompatible: (PackageManifest) -> Boolean,
|
||||
) {
|
||||
// ensure that diff does not include internal keys
|
||||
DENY_LIST.forEach { forbiddenKey ->
|
||||
@@ -123,7 +123,7 @@ internal interface VersionDaoInt : VersionDao {
|
||||
}
|
||||
// diff version
|
||||
val diffedVersion = ReflectionDiffer.applyDiff(version, jsonObject)
|
||||
val isCompatible = checkIfCompatible(diffedVersion.manifest.toManifestV2())
|
||||
val isCompatible = checkIfCompatible(diffedVersion.packageManifest)
|
||||
update(diffedVersion.copy(isCompatible = isCompatible))
|
||||
// diff versioned strings
|
||||
val manifest = jsonObject["manifest"]
|
||||
|
||||
Reference in New Issue
Block a user