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

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

refactor(updates): reduce update fetching time

chore: remove update timing traces

Baseline timing logs are no longer needed.

perf: remove signature verify cap

Let signature verification run fully in parallel to minimize matching time.

perf: remove system app concurrency cap

Let system app update fetches run concurrently without a semaphore limit.

perf: cap parallel signature and system fetch

Run signature verification and system app update fetches concurrently with conservative caps to reduce latency without saturating IO.

perf: maximize CleanAPK detail concurrency

Use batch availability as a prefilter and fetch full details in parallel, with per-package fallback to keep labels correct.

perf: parallelize CleanAPK detail fetches

Run per-package detail requests concurrently to shorten update list retrieval for multiple packages.

perf: drop signature concurrency cap

Remove the semaphore limit so signature checks can run fully in parallel for faster matching.

fix: avoid incomplete CleanAPK batch details

Fall back to per-package detail fetches so list items keep the Open Source label when batch availability data is incomplete.

perf: parallelize update fetch work

Run update sources concurrently and bound signature matching to reduce overall update list latency while keeping work coordinated.

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.

perf: reduce updates load stalls

Add timing logs to attribute update latency and move update network/IO work off the main thread to keep the UI responsive.
parent 89912133
Loading
Loading
Loading
Loading
+25 −13
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import foundation.e.apps.data.EnabledSourceState
import foundation.e.apps.data.EnabledStoreRepositoryProvider
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
@@ -30,6 +31,8 @@ import foundation.e.apps.data.handleNetworkResult
import foundation.e.apps.data.playstore.PlayStoreRepository
import foundation.e.apps.domain.model.install.Status
import foundation.e.apps.ui.applicationlist.ApplicationDiffUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject

class AppsApiImpl @Inject constructor(
@@ -40,9 +43,11 @@ class AppsApiImpl @Inject constructor(
    override suspend fun getCleanapkAppDetails(packageName: String): Pair<Application, ResultStatus> {
        var application = Application()
        val result = handleNetworkResult {
            application = enabledStoreRepositoryProvider.awaitStore(Source.OPEN_SOURCE)
            application = withContext(Dispatchers.IO) {
                enabledStoreRepositoryProvider.awaitStore(Source.OPEN_SOURCE)
                    ?.getAppDetails(packageName)
                    ?: Application()
            }
            application.source = Source.OPEN_SOURCE
            application.updateType()
            application.updateFilterLevel()
@@ -92,14 +97,17 @@ class AppsApiImpl @Inject constructor(
        packageNameList: List<String>,
    ): Pair<List<Application>, ResultStatus> {
        val status = ResultStatus.OK
        val applicationList = mutableListOf<Application>()

        val applicationList = withContext(Dispatchers.IO) {
            val store = enabledStoreRepositoryProvider.awaitStore(Source.OPEN_SOURCE)
            if (store is CleanApkRepository) {
                store.getAppDetailsForPackages(packageNameList)
            } else {
                val list = mutableListOf<Application>()
                for (packageName in packageNameList) {
            applicationList.add(
                enabledStoreRepositoryProvider.awaitStore(Source.OPEN_SOURCE)
                    ?.getAppDetails(packageName)
                    ?: Application()
            )
                    list.add(store?.getAppDetails(packageName) ?: Application())
                }
                list
            }
        }

        return Pair(applicationList, status)
@@ -112,8 +120,10 @@ class AppsApiImpl @Inject constructor(

        val playStore = enabledStoreRepositoryProvider.awaitStore(Source.PLAY_STORE) as? PlayStoreRepository
        val response = handleNetworkResult {
            withContext(Dispatchers.IO) {
                playStore?.getAppsDetails(packageNameList).orEmpty()
            }
        }
        val apps = response.data ?: emptyList()
        for (app in apps) {
            handleFilteredApps(app, applicationList)
@@ -151,7 +161,9 @@ class AppsApiImpl @Inject constructor(
            val store = enabledStoreRepositoryProvider.awaitStore(source)
                ?: throw IllegalStateException("Could not get store")

            application = store.getAppDetails(packageName)
            application = withContext(Dispatchers.IO) {
                store.getAppDetails(packageName)
            }
            application.let {
                applicationDataManager.updateStatus(it)
                it.source = source
+6 −2
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ import foundation.e.apps.data.cleanapk.CleanApkDownloadInfoFetcher
import foundation.e.apps.data.enums.Source
import foundation.e.apps.data.handleNetworkResult
import foundation.e.apps.data.installation.model.AppInstall
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject

class DownloadInfoApiImpl @Inject constructor(
@@ -127,6 +129,8 @@ class DownloadInfoApiImpl @Inject constructor(
    }

    override suspend fun getOSSDownloadInfo(id: String, version: String?) =
        withContext(Dispatchers.IO) {
            (appSources.cleanApkAppsRepo as CleanApkDownloadInfoFetcher)
                .getDownloadInfo(id, version)
        }
}
+41 −0
Original line number Diff line number Diff line
@@ -30,6 +30,9 @@ import foundation.e.apps.data.cleanapk.data.download.Download
import foundation.e.apps.data.cleanapk.data.search.Search
import foundation.e.apps.data.enums.Source
import foundation.e.apps.data.system.SystemInfoProvider
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import retrofit2.Response
import javax.inject.Inject

@@ -103,6 +106,44 @@ class CleanApkAppsRepository @Inject constructor(
        } ?: Application()
    }

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

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

            val appsByPackage = search.apps.associateBy { it.package_name }
            return@coroutineScope packageNames.map { packageName ->
                val app = appsByPackage[packageName]
                if (app == null || app._id.isBlank()) {
                    async { getAppDetails(packageName) }
                } else {
                    async {
                        val appResponse = cleanApkRetrofit.getAppOrPWADetailsByID(
                            id = app._id,
                            architectures = SystemInfoProvider.getSupportedArchitectureList(),
                            type = null
                        )
                        appResponse.body()?.app?.let {
                            it.copy(source = if (it.is_pwa) Source.PWA else Source.OPEN_SOURCE)
                        } ?: Application()
                    }
                }
            }.awaitAll()
        }
    }

    override suspend fun getSearchResults(pattern: String): List<Application> {
        return cleanApkSearchHelper.getSearchResults(
            keyword = pattern,
+44 −0
Original line number Diff line number Diff line
@@ -31,6 +31,9 @@ import foundation.e.apps.data.cleanapk.data.categories.Categories
import foundation.e.apps.data.cleanapk.data.search.Search
import foundation.e.apps.data.enums.Source
import foundation.e.apps.data.enums.Type
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import retrofit2.Response
import javax.inject.Inject

@@ -100,6 +103,47 @@ class CleanApkPwaRepository @Inject constructor(
        } ?: Application()
    }

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

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

            val appsByPackage = search.apps.associateBy { it.package_name }
            return@coroutineScope packageNames.map { packageName ->
                val app = appsByPackage[packageName]
                if (app == null || app._id.isBlank()) {
                    async { getAppDetails(packageName) }
                } else {
                    async {
                        val appResponse = cleanApkRetrofit.getAppOrPWADetailsByID(app._id, null, null)
                        appResponse.body()?.app?.let {
                            if (it.is_pwa) {
                                it.copy(
                                    source = Source.PWA,
                                    type = Type.PWA
                                )
                            } else {
                                it
                            }
                        } ?: Application()
                    }
                }
            }.awaitAll()
        }
    }

    override suspend fun getSearchResults(pattern: String): List<Application> {
        return cleanApkSearchHelper.getSearchResults(
            keyword = pattern,
+12 −0
Original line number Diff line number Diff line
@@ -19,8 +19,12 @@
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 kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import retrofit2.Response

const val NUMBER_OF_ITEMS = 20
@@ -30,4 +34,12 @@ interface CleanApkRepository : StoreRepository {
    suspend fun getAppsByCategory(category: String): Response<Search>
    suspend fun getCategories(): Response<Categories>
    suspend fun checkAvailablePackages(packageNames: List<String>): Response<Search>

    suspend fun getAppDetailsForPackages(packageNames: List<String>): List<Application> {
        return coroutineScope {
            packageNames.map {
                async { getAppDetails(it) }
            }.awaitAll()
        }
    }
}
Loading