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

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

refactor(updates): fetch update sources concurrently

Open-source, GPlay, and system app update checks are independent network calls that were running sequentially. Running them in parallel cuts total wait time from the sum of all sources to the slowest single source.
parent 0a0802f0
Loading
Loading
Loading
Loading
+93 −68
Original line number Diff line number Diff line
@@ -61,10 +61,67 @@ class UpdatesManagerImpl @Inject constructor(
    private val userApplications: List<ApplicationInfo>
        get() = appLoungePackageManager.getAllUserApps()

    suspend fun getUpdates(): Pair<List<Application>, ResultStatus> {
    private data class UpdateCandidates(
        val openSource: List<String>,
        val gplay: List<String>,
    )

    suspend fun getUpdates(): Pair<List<Application>, ResultStatus> = coroutineScope {
        val candidates = collectUpdateCandidates()

        val openSourceDeferred = if (candidates.openSource.isNotEmpty()) {
            async {
                getUpdatesFromApi {
                    applicationRepository.getApplicationDetails(
                        candidates.openSource,
                        Source.OPEN_SOURCE
                    )
                }
            }
        } else {
            null
        }

        val gplayDeferred = if (getApplicationCategoryPreference().contains(ApplicationRepository.APP_TYPE_ANY) &&
            candidates.gplay.isNotEmpty()
        ) {
            async {
                getUpdatesFromApi {
                    getGPlayUpdates(candidates.gplay)
                }
            }
        } else {
            null
        }

        val systemAppsDeferred = async {
            getSystemAppUpdates()
        }

        val openSourceResult = openSourceDeferred?.await()
        val gplayResult = gplayDeferred?.await()
        val systemApps = systemAppsDeferred.await()

        val updateList = mutableListOf<Application>()
        var status = ResultStatus.OK
        updateList.addAll(openSourceResult?.first.orEmpty())
        updateList.addAll(gplayResult?.first.orEmpty())
        val nonFaultyUpdateList = faultyAppRepository.removeFaultyApps(updateList)

        addSystemApps(updateList, nonFaultyUpdateList, systemApps)

        var status = openSourceResult?.second ?: ResultStatus.OK
        if (gplayResult != null && status != ResultStatus.OK) {
            status = gplayResult.second
        }

        return@coroutineScope Pair(updateList, status)
    }

    /**
     * Collects installed apps eligible for update checking,
     * categorized by their update source.
     */
    private suspend fun collectUpdateCandidates(): UpdateCandidates {
        val openSourceInstalledApps = getOpenSourceInstalledApps().toMutableList()
        val gPlayInstalledApps = getGPlayInstalledApps().toMutableList()

@@ -89,52 +146,13 @@ class UpdatesManagerImpl @Inject constructor(
            }
        }

        openSourceInstalledApps.removeIf {
            blockedAppRepository.isBlockedApp(it)
        }

        gPlayInstalledApps.removeIf {
            blockedAppRepository.isBlockedApp(it)
        }

        // Get open source app updates
        if (openSourceInstalledApps.isNotEmpty()) {
            status = getUpdatesFromApi({
                applicationRepository.getApplicationDetails(
                    openSourceInstalledApps,
                    Source.OPEN_SOURCE
                )
            }, updateList)
        }

        // Get GPlay app updates
        if (getApplicationCategoryPreference().contains(ApplicationRepository.APP_TYPE_ANY) &&
            gPlayInstalledApps.isNotEmpty()
        ) {
            val gplayStatus = getUpdatesFromApi({
                getGPlayUpdates(
                    gPlayInstalledApps
                )
            }, updateList)

            /**
             If any one of the sources is successful, status should be [ResultStatus.OK]
             **/
            status = if (status == ResultStatus.OK) status else gplayStatus
        }

        val systemApps = getSystemAppUpdates()
        val nonFaultyUpdateList = faultyAppRepository.removeFaultyApps(updateList)
        openSourceInstalledApps.removeIf { blockedAppRepository.isBlockedApp(it) }
        gPlayInstalledApps.removeIf { blockedAppRepository.isBlockedApp(it) }

        addSystemApps(updateList, nonFaultyUpdateList, systemApps)

        return Pair(updateList, status)
        return UpdateCandidates(openSourceInstalledApps, gPlayInstalledApps)
    }

    suspend fun getUpdatesOSS(): Pair<List<Application>, ResultStatus> {
        val updateList = mutableListOf<Application>()
        var status = ResultStatus.OK

    suspend fun getUpdatesOSS(): Pair<List<Application>, ResultStatus> = coroutineScope {
        val openSourceInstalledApps = getOpenSourceInstalledApps().toMutableList()

        if (appPreferencesRepository.shouldUpdateAppsFromOtherStores()) {
@@ -158,29 +176,41 @@ class UpdatesManagerImpl @Inject constructor(
            blockedAppRepository.isBlockedApp(it)
        }

        if (openSourceInstalledApps.isNotEmpty()) {
            status = getUpdatesFromApi({
        val openSourceDeferred = if (openSourceInstalledApps.isNotEmpty()) {
            async {
                getUpdatesFromApi {
                    applicationRepository.getApplicationDetails(
                        openSourceInstalledApps,
                        Source.OPEN_SOURCE
                    )
            }, updateList)
                }
            }
        } else {
            null
        }

        val systemAppsDeferred = async {
            getSystemAppUpdates()
        }

        val systemApps = getSystemAppUpdates()
        val openSourceResult = openSourceDeferred?.await()
        val systemApps = systemAppsDeferred.await()

        val updateList = mutableListOf<Application>()
        updateList.addAll(openSourceResult?.first.orEmpty())
        val nonFaultyUpdateList = faultyAppRepository.removeFaultyApps(updateList)

        addSystemApps(updateList, nonFaultyUpdateList, systemApps)

        return Pair(updateList, status)
        val status = openSourceResult?.second ?: ResultStatus.OK

        return@coroutineScope Pair(updateList, status)
    }

    private suspend fun getSystemAppUpdates(): List<Application> {
        val systemApps = mutableListOf<Application>()
        getUpdatesFromApi({
        return getUpdatesFromApi {
            Pair(systemAppsUpdatesRepository.getSystemUpdates(), ResultStatus.OK)
        }, systemApps)
        return systemApps
        }.first
    }

    /**
@@ -263,26 +293,21 @@ class UpdatesManagerImpl @Inject constructor(
    }

    /**
     * Runs API (GPlay api or CleanApk) and accumulates the updatable apps
     * into a provided list.
     * Calls an API function and filters the results to only updatable apps.
     *
     * @param apiFunction Function that calls an API method to fetch update information.
     * Apps returned is filtered to get only the apps which can be downloaded and updated.
     * @param updateAccumulationList A list into which the filtered results from
     * [apiFunction] is stored. The caller needs to read this list to get the update info.
     * Apps returned are filtered to only those which can be downloaded and updated.
     *
     * @return ResultStatus from calling [apiFunction].
     * @return A pair of the filtered updatable apps list and the [ResultStatus] from [apiFunction].
     */
    private suspend fun getUpdatesFromApi(
        apiFunction: suspend () -> Pair<List<Application>, ResultStatus>,
        updateAccumulationList: MutableList<Application>,
    ): ResultStatus {
    ): Pair<List<Application>, ResultStatus> {
        val apiResult = apiFunction()
        val updatableApps = apiResult.first.filter {
            it.status == Status.UPDATABLE && (it.filterLevel.isUnFiltered() || it.isFDroidApp)
        }
        updateAccumulationList.addAll(updatableApps)
        return apiResult.second
        return Pair(updatableApps, apiResult.second)
    }

    private suspend fun getGPlayUpdates(