Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Verified Commit abe54c23 authored by Fahim M. Choudhury's avatar Fahim M. Choudhury
Browse files

perf: cut update detail round trips

Batch CleanAPK package lookups and reuse fetched details during signature checks to reduce repeated network calls and installer scans.
parent a8b414ea
Loading
Loading
Loading
Loading
+10 −4
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ package foundation.e.apps.data.application.apps
import foundation.e.apps.data.Stores
import foundation.e.apps.data.application.ApplicationDataManager
import foundation.e.apps.data.application.data.Application
import foundation.e.apps.data.cleanapk.repositories.CleanApkRepository
import foundation.e.apps.data.enums.FilterLevel
import foundation.e.apps.data.enums.ResultStatus
import foundation.e.apps.data.enums.Source
@@ -93,12 +94,17 @@ class AppsApiImpl @Inject constructor(
    ): Pair<List<Application>, ResultStatus> {
        val status = ResultStatus.OK
        val applicationList = withContext(Dispatchers.IO) {
            val store = stores.getStore(Source.OPEN_SOURCE)
            if (store is CleanApkRepository) {
                store.getAppDetailsForPackages(packageNameList)
            } else {
                val list = mutableListOf<Application>()
                for (packageName in packageNameList) {
                list.add(stores.getStore(Source.OPEN_SOURCE)?.getAppDetails(packageName) ?: Application())
                    list.add(store?.getAppDetails(packageName) ?: Application())
                }
                list
            }
        }

        return Pair(applicationList, status)
    }
+33 −0
Original line number Diff line number Diff line
@@ -106,6 +106,39 @@ class CleanApkAppsRepository @Inject constructor(
        } ?: Application()
    }

    override suspend fun getAppDetailsForPackages(packageNames: List<String>): List<Application> {
        if (packageNames.isEmpty()) {
            return emptyList()
        }

        val response = cleanApkRetrofit.checkAvailablePackages(
            packages = packageNames,
            architectures = SystemInfoProvider.getSupportedArchitectureList()
        )
        val search = response.body()
        if (!response.isSuccessful || search?.success != true) {
            return packageNames.map { getAppDetails(it) }
        }

        val apps = search.apps
        val appsByPackage = apps.associateBy { it.package_name }

        val details = mutableListOf<Application>()
        for (packageName in packageNames) {
            val app = appsByPackage[packageName] ?: continue
            val response = cleanApkRetrofit.getAppOrPWADetailsByID(
                id = app._id,
                architectures = SystemInfoProvider.getSupportedArchitectureList(),
                type = null
            )
            response.body()?.app?.let {
                details.add(it.copy(source = if (it.is_pwa) Source.PWA else Source.OPEN_SOURCE))
            }
        }

        return details
    }

    override suspend fun getSearchResults(pattern: String): List<Application> {
        return cleanApkSearchHelper.getSearchResults(
            keyword = pattern,
+38 −0
Original line number Diff line number Diff line
@@ -100,6 +100,44 @@ class CleanApkPwaRepository @Inject constructor(
        } ?: Application()
    }

    override suspend fun getAppDetailsForPackages(packageNames: List<String>): List<Application> {
        if (packageNames.isEmpty()) {
            return emptyList()
        }

        val response = cleanApkRetrofit.checkAvailablePackages(
            packageNames,
            CleanApkRetrofit.APP_TYPE_PWA
        )
        val search = response.body()
        if (!response.isSuccessful || search?.success != true) {
            return packageNames.map { getAppDetails(it) }
        }

        val apps = search.apps
        val appsByPackage = apps.associateBy { it.package_name }

        val details = mutableListOf<Application>()
        for (packageName in packageNames) {
            val app = appsByPackage[packageName] ?: continue
            val response = cleanApkRetrofit.getAppOrPWADetailsByID(app._id, null, null)
            response.body()?.app?.let {
                details.add(
                    if (it.is_pwa) {
                        it.copy(
                            source = Source.PWA,
                            type = Type.PWA
                        )
                    } else {
                        it
                    }
                )
            }
        }

        return details
    }

    override suspend fun getSearchResults(pattern: String): List<Application> {
        return cleanApkSearchHelper.getSearchResults(
            keyword = pattern,
+5 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@
package foundation.e.apps.data.cleanapk.repositories

import foundation.e.apps.data.StoreRepository
import foundation.e.apps.data.application.data.Application
import foundation.e.apps.data.cleanapk.data.categories.Categories
import foundation.e.apps.data.cleanapk.data.search.Search
import retrofit2.Response
@@ -30,4 +31,8 @@ interface CleanApkRepository : StoreRepository {
    suspend fun getAppsByCategory(category: String, paginationParameter: Any? = null): Response<Search>
    suspend fun getCategories(): Response<Categories>
    suspend fun checkAvailablePackages(packageNames: List<String>): Response<Search>

    suspend fun getAppDetailsForPackages(packageNames: List<String>): List<Application> {
        return packageNames.map { getAppDetails(it) }
    }
}
+49 −11
Original line number Diff line number Diff line
@@ -82,12 +82,14 @@ class UpdatesManagerImpl @Inject constructor(
        if (appPreferencesRepository.shouldUpdateAppsFromOtherStores()) {
            withContext(Dispatchers.IO) {
                val otherStoresInstalledApps = logInstalledApps("other-store") {
                    getAppsFromOtherStores()
                    getAppsFromOtherStores(openSourceInstalledApps, gPlayInstalledApps)
                }.toMutableList()

                val cleanApkAppsByPackage = getCleanApkDetailsByPackage(otherStoresInstalledApps)

                // This list is based on app signatures
                val updatableFDroidApps =
                    findPackagesMatchingFDroidSignatures(otherStoresInstalledApps)
                    findPackagesMatchingFDroidSignatures(otherStoresInstalledApps, cleanApkAppsByPackage)

                openSourceInstalledApps.addAll(updatableFDroidApps)

@@ -161,12 +163,14 @@ class UpdatesManagerImpl @Inject constructor(

        if (appPreferencesRepository.shouldUpdateAppsFromOtherStores()) {
            val otherStoresInstalledApps = logInstalledApps("OSS other-store") {
                getAppsFromOtherStores()
                getAppsFromOtherStores(openSourceInstalledApps, emptyList())
            }.toMutableList()

            val cleanApkAppsByPackage = getCleanApkDetailsByPackage(otherStoresInstalledApps)

            // This list is based on app signatures
            val updatableFDroidApps =
                findPackagesMatchingFDroidSignatures(otherStoresInstalledApps)
                findPackagesMatchingFDroidSignatures(otherStoresInstalledApps, cleanApkAppsByPackage)

            openSourceInstalledApps.addAll(updatableFDroidApps)
        }
@@ -271,8 +275,11 @@ class UpdatesManagerImpl @Inject constructor(
     * @return List of package names of apps installed from other app stores like
     * Aurora Store, Apk mirror, apps installed from browser, apps from ADB etc.
     */
    private fun getAppsFromOtherStores(): List<String> {
        val gplayAndOpenSourceInstalledApps = getGPlayInstalledApps() + getOpenSourceInstalledApps()
    private fun getAppsFromOtherStores(
        openSourceInstalledApps: List<String>,
        gPlayInstalledApps: List<String>,
    ): List<String> {
        val gplayAndOpenSourceInstalledApps = (gPlayInstalledApps + openSourceInstalledApps).toSet()
        return userApplications.filter {
            it.packageName !in gplayAndOpenSourceInstalledApps
        }.map { it.packageName }
@@ -326,7 +333,10 @@ class UpdatesManagerImpl @Inject constructor(
     *
     * Map is String : String = package name : signature
     */
    private suspend fun getFDroidAppsAndSignatures(installedPackageNames: List<String>): Map<String, String> {
    private suspend fun getFDroidAppsAndSignatures(
        installedPackageNames: List<String>,
        cleanApkAppsByPackage: Map<String, Application?>,
    ): Map<String, String> {
        val startMs = nowMs()
        Timber.tag("FAHIM").i(
            "Updates signature scan start, packages=%d",
@@ -336,7 +346,7 @@ class UpdatesManagerImpl @Inject constructor(
        for (packageName in installedPackageNames) {
            val perPackageStartMs = nowMs()
            val beforeCount = appsAndSignatures.size
            updateAppsWithPGPSignature(packageName, appsAndSignatures)
            updateAppsWithPGPSignature(packageName, appsAndSignatures, cleanApkAppsByPackage)
            Timber.tag("FAHIM").i(
                "Updates signature candidate done, package=%s, added=%s, duration=%s",
                packageName,
@@ -354,10 +364,16 @@ class UpdatesManagerImpl @Inject constructor(

    private suspend fun updateAppsWithPGPSignature(
        packageName: String,
        appsAndSignatures: HashMap<String, String>
        appsAndSignatures: HashMap<String, String>,
        cleanApkAppsByPackage: Map<String, Application?>,
    ) {
        val appDetailsStartMs = nowMs()
        val apps = applicationRepository.getApplicationDetails(listOf(packageName), Source.OPEN_SOURCE).first
        val cleanApkApplication = cleanApkAppsByPackage[packageName]
        val apps = when {
            cleanApkApplication != null -> listOf(cleanApkApplication)
            cleanApkAppsByPackage.containsKey(packageName) -> emptyList()
            else -> applicationRepository.getApplicationDetails(listOf(packageName), Source.OPEN_SOURCE).first
        }
        Timber.tag("FAHIM").i(
            "Updates signature app details done, package=%s, duration=%s, resultCount=%d",
            packageName,
@@ -427,9 +443,13 @@ class UpdatesManagerImpl @Inject constructor(
     */
    private suspend fun findPackagesMatchingFDroidSignatures(
        installedPackageNames: List<String>,
        cleanApkAppsByPackage: Map<String, Application?>,
    ): List<String> {
        val signatureStartMs = nowMs()
        val fDroidAppsAndSignatures = getFDroidAppsAndSignatures(installedPackageNames)
        val fDroidAppsAndSignatures = getFDroidAppsAndSignatures(
            installedPackageNames,
            cleanApkAppsByPackage,
        )

        val fDroidUpdatablePackageNames = mutableListOf<String>()
        fDroidAppsAndSignatures.forEach { (packageName, signature) ->
@@ -486,6 +506,24 @@ class UpdatesManagerImpl @Inject constructor(
        return fDroidUpdatablePackageNames
    }

    private suspend fun getCleanApkDetailsByPackage(
        packageNames: List<String>,
    ): Map<String, Application?> {
        if (packageNames.isEmpty()) {
            return emptyMap()
        }

        val appsResult = applicationRepository.getApplicationDetails(packageNames, Source.OPEN_SOURCE)
        if (appsResult.second != ResultStatus.OK) {
            return emptyMap()
        }

        val appsByPackage = appsResult.first
            .filter { it.package_name.isNotBlank() }
            .associateBy { it.package_name }
        return packageNames.associateWith { appsByPackage[it] }
    }

    /**
     * Get signature version for the installed version of the app.
     * A signature version is like "update_XX" where XX is a 2 digit number.