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

Commit 7c9269fe authored by Jonathan Klee's avatar Jonathan Klee
Browse files

fix: isolate source home loading

Use supervisorScope to not cancel all children when a child gets cancelled.
parent 7bb7a8cf
Loading
Loading
Loading
Loading
Loading
+30 −3
Original line number Original line Diff line number Diff line
@@ -35,8 +35,11 @@ import foundation.e.apps.data.enums.Source
import foundation.e.apps.data.enums.Status
import foundation.e.apps.data.enums.Status
import foundation.e.apps.data.handleNetworkResult
import foundation.e.apps.data.handleNetworkResult
import foundation.e.apps.data.install.models.AppInstall
import foundation.e.apps.data.install.models.AppInstall
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.async
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.isActive
import kotlinx.coroutines.supervisorScope
import javax.inject.Inject
import javax.inject.Inject
import javax.inject.Singleton
import javax.inject.Singleton


@@ -76,7 +79,7 @@ class ApplicationRepository @Inject constructor(
                )
                )
            }
            }


            coroutineScope {
            supervisorScope {
                val resultsBySource = mutableMapOf<Source, ResultSupreme<List<Home>>>()
                val resultsBySource = mutableMapOf<Source, ResultSupreme<List<Home>>>()
                val sources = stores.getStores().keys
                val sources = stores.getStores().keys
                val deferredResults = sources.associateWith { source ->
                val deferredResults = sources.associateWith { source ->
@@ -84,7 +87,14 @@ class ApplicationRepository @Inject constructor(
                }
                }


                sources.forEach { source ->
                sources.forEach { source ->
                    val result = deferredResults.getValue(source).await()
                    val result = try {
                        deferredResults.getValue(source).await()
                    } catch (exception: CancellationException) {
                        if (!currentCoroutineContext().isActive) {
                            throw exception
                        }
                        createSourceFailureResult(source, exception)
                    }
                    resultsBySource[source] = result
                    resultsBySource[source] = result
                    emitResult(result, resultsBySource)
                    emitResult(result, resultsBySource)
                }
                }
@@ -123,6 +133,23 @@ class ApplicationRepository @Inject constructor(
        }
        }
    }
    }


    private fun createSourceFailureResult(
        source: Source,
        exception: Exception,
    ): ResultSupreme<List<Home>> {
        val status = ResultStatus.UNKNOWN.apply {
            message = exception.message.orEmpty()
        }

        setHomeErrorMessage(status, source)
        return ResultSupreme.create(
            status = status,
            data = emptyList(),
            message = status.message,
            exception = exception,
        )
    }

    private fun mergeResults(results: Collection<ResultSupreme<List<Home>>>): List<Home> {
    private fun mergeResults(results: Collection<ResultSupreme<List<Home>>>): List<Home> {
        val merged = results.flatMap { it.data.orEmpty() }.toMutableList()
        val merged = results.flatMap { it.data.orEmpty() }.toMutableList()
        merged.sortBy {
        merged.sortBy {
+28 −0
Original line number Original line Diff line number Diff line
@@ -45,6 +45,7 @@ import io.mockk.coVerify
import io.mockk.every
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockk
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.runTest
@@ -191,6 +192,33 @@ class ApplicationRepositoryHomeTest {
            .containsExactly(ApplicationRepository.APP_TYPE_ANY)
            .containsExactly(ApplicationRepository.APP_TYPE_ANY)
    }
    }


    @Test
    fun fetchHomeScreenDataKeepsOtherSourcesWhenOneSourceIsCancelled() = runTest {
        val cancelledStore = object : StoreRepository {
            override suspend fun getHomeScreenData(list: MutableList<Home>): List<Home> {
                throw CancellationException("request cancelled")
            }
            override suspend fun getAppDetails(packageName: String) = Application()
            override suspend fun getSearchResults(pattern: String) = emptyList<Application>()
            override suspend fun getSearchSuggestions(pattern: String) = emptyList<SearchSuggestion>()
        }
        val repositories = linkedMapOf(
            Source.PLAY_STORE to fakeStore(playStoreHome),
            Source.OPEN_SOURCE to cancelledStore,
        )
        every { stores.getStores() } returns repositories
        every { stores.getStore(Source.PLAY_STORE) } returns repositories.getValue(Source.PLAY_STORE)
        every { stores.getStore(Source.OPEN_SOURCE) } returns cancelledStore

        val emissions = applicationRepository.getHomeScreenData().asFlow().take(2).toList()

        val result = emissions.last()
        assertThat(result.isUnknownError()).isTrue()
        assertThat(result.message).contains("request cancelled")
        assertThat(result.data?.map { it.source })
            .containsExactly(ApplicationRepository.APP_TYPE_ANY)
    }

    @Test
    @Test
    fun getSelectedAppTypesReturnsEnabledStores() {
    fun getSelectedAppTypesReturnsEnabledStores() {
        every { stores.isStoreEnabled(Source.PLAY_STORE) } returns true
        every { stores.isStoreEnabled(Source.PLAY_STORE) } returns true