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

Commit 8a3f3892 authored by Abhishek Aggarwal's avatar Abhishek Aggarwal
Browse files

fix(chore): Drain full steam cluster for categories

parent 09eaa6cf
Loading
Loading
Loading
Loading
Loading
+63 −15
Original line number Diff line number Diff line
@@ -25,12 +25,14 @@ import com.aurora.gplayapi.data.models.PlayFile
import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.exceptions.GooglePlayException
import com.aurora.gplayapi.helpers.AppDetailsHelper
import com.aurora.gplayapi.helpers.CategoryHelper
import com.aurora.gplayapi.helpers.ContentRatingHelper
import com.aurora.gplayapi.helpers.PurchaseHelper
import com.aurora.gplayapi.helpers.contracts.CategoryStreamContract
import com.aurora.gplayapi.helpers.contracts.StreamContract
import com.aurora.gplayapi.helpers.contracts.TopChartsContract.Chart
import com.aurora.gplayapi.helpers.contracts.TopChartsContract.Type
import com.aurora.gplayapi.helpers.web.WebAppDetailsHelper
import com.aurora.gplayapi.helpers.web.WebCategoryHelper
import com.aurora.gplayapi.helpers.web.WebCategoryStreamHelper
import com.aurora.gplayapi.helpers.web.WebTopChartsHelper
import dagger.hilt.android.qualifiers.ApplicationContext
@@ -116,14 +118,31 @@ class PlayStoreRepository @Inject constructor(
    )

    fun getAppsByCategory(category: String, pageUrl: String?): StreamCluster {
        val subCategoryHelper = WebCategoryStreamHelper().using(gPlayHttpClient)
        val helper = WebCategoryStreamHelper().using(gPlayHttpClient)

        if (!pageUrl.isNullOrEmpty()) {
            return subCategoryHelper.nextStreamCluster(id = -1, nextPageUrl = pageUrl)
        val bundle = if (!pageUrl.isNullOrEmpty()) {
            helper.nextStreamBundle(
                id = -1,
                category = StreamContract.Category.NONE,
                nextPageToken = pageUrl,
            )
        } else {
            helper.fetch(upperCaseCategory(category))
        }

        val bundle = subCategoryHelper.fetch(upperCaseCategory(category))
        return bundle.streamClusters.entries.first().value
        val drainedClusters = bundle.streamClusters.values.map { cluster ->
            drainCluster(helper, cluster)
        }

        val mergedApps = drainedClusters
            .flatMap { it.clusterAppList }
            .distinctBy { it.packageName }

        return StreamCluster(
            id = bundle.id,
            clusterAppList = mergedApps.toMutableList(),
            clusterNextPageUrl = bundle.streamNextPageUrl,
        )
    }

    private fun upperCaseCategory(path: String): String {
@@ -132,17 +151,42 @@ class PlayStoreRepository @Inject constructor(
        return "$basePath/$lastPart"
    }

    suspend fun getCategories(type: CategoryType?): List<Category> {
        val categoryList = mutableListOf<Category>()
        if (type == null) {
            return categoryList
        }

        withContext(Dispatchers.IO) {
            val categoryHelper = WebCategoryHelper().using(gPlayHttpClient)
            categoryList.addAll(categoryHelper.getAllCategories(getCategoryType(type)))
        }
        return categoryList
    /**
     * Walks a cluster's pagination chain to fatten the category landing page, capped at
     * [MAX_CLUSTER_PAGES] pages to bound load time and rate-limit cost. Apps in pages beyond the
     * cap are dropped — acceptable because category browse is a one-shot fetch (no load-more),
     * so the returned cursor is informational only.
     */
    private fun drainCluster(
        helper: CategoryStreamContract,
        first: StreamCluster,
    ): StreamCluster {
        val apps = first.clusterAppList.toMutableList()
        var nextUrl = first.clusterNextPageUrl
        var page = 0
        while (nextUrl.isNotBlank() && page < MAX_CLUSTER_PAGES) {
            val next = helper.nextStreamCluster(id = first.id, nextPageUrl = nextUrl)
            apps += next.clusterAppList
            // Guard against a stuck cursor: if Play hands back the same nextPageUrl, stop
            // instead of looping forever within the page cap.
            if (next.clusterNextPageUrl == nextUrl) break
            nextUrl = next.clusterNextPageUrl
            page++
        }
        return first.copy(clusterAppList = apps, clusterNextPageUrl = nextUrl)
    }

    suspend fun getCategories(type: CategoryType?): List<Category> = withContext(Dispatchers.IO) {
        if (type == null) return@withContext emptyList()
        executeWithPlayAuthRecovery(
            operationName = "categories",
            request = {
                val helper = CategoryHelper(
                    playStoreAuthManager.requireValidatedPlayStoreAuth()
                ).using(gPlayHttpClient)
                helper.getAllCategories(getCategoryType(type))
            },
        )
    }

    // batch request response might not contain images and descriptions of the app
@@ -471,4 +515,8 @@ class PlayStoreRepository @Inject constructor(
            )
        }
    }

    companion object {
        private const val MAX_CLUSTER_PAGES = 5
    }
}