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

Commit ea7f47e2 authored by Hasib Prince's avatar Hasib Prince
Browse files

error handling updated for search and homepage

parent d1a46c82
Loading
Loading
Loading
Loading
+16 −2
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ package foundation.e.apps.data
import foundation.e.apps.data.enums.ResultStatus
import java.util.concurrent.TimeoutException

private const val UNKNOWN_ERROR = "Unknown error!"

/**
 * Another implementation of Result class.
 * This removes the use of [ResultStatus] class for different status.
@@ -52,10 +54,12 @@ sealed class ResultSupreme<T> {
     * Example can be an empty list.
     * @param exception Optional exception from try-catch block.
     */
    class Timeout<T>(data: T, exception: Exception = TimeoutException()) :
    class Timeout<T>(data: T? = null, exception: Exception = TimeoutException()) :
        ResultSupreme<T>() {
        init {
            setData(data)
            data?.let {
                setData(it)
            }
            this.exception = exception
        }
    }
@@ -119,6 +123,16 @@ sealed class ResultSupreme<T> {
        this.data = data
    }

    fun getResultStatus(): ResultStatus {
        return when(this) {
            is Success -> ResultStatus.OK
            is Timeout -> ResultStatus.TIMEOUT
            else -> ResultStatus.UNKNOWN.apply {
                message = this@ResultSupreme.exception?.localizedMessage?: UNKNOWN_ERROR
            }
        }
    }

    companion object {

        /**
+5 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import foundation.e.apps.data.ecloud.EcloudApiInterface
import foundation.e.apps.data.exodus.ExodusTrackerApi
import foundation.e.apps.data.fdroid.FdroidApiInterface
import foundation.e.apps.data.fdroid.FdroidWebInterface
import foundation.e.apps.data.gplay.utils.GPlayHttpClient
import okhttp3.Cache
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaTypeOrNull
@@ -48,6 +49,7 @@ import retrofit2.converter.moshi.MoshiConverterFactory
import timber.log.Timber
import java.net.ConnectException
import java.util.Locale
import java.util.concurrent.TimeUnit
import javax.inject.Named
import javax.inject.Singleton

@@ -55,6 +57,8 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
object RetrofitModule {

    private const val HTTP_TIMEOUT_IN_SECOND = 10L

    /**
     * Provides an instance of Retrofit to work with CleanAPK API
     * @return instance of [CleanApkRetrofit]
@@ -208,6 +212,7 @@ object RetrofitModule {
    fun provideOkHttpClient(cache: Cache, interceptor: Interceptor): OkHttpClient {
        return OkHttpClient.Builder()
            .addInterceptor(interceptor)
            .callTimeout(HTTP_TIMEOUT_IN_SECOND, TimeUnit.SECONDS)
            .cache(cache)
            .build()
    }
+48 −28
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import android.content.Context
import android.text.format.Formatter
import androidx.lifecycle.LiveData
import androidx.lifecycle.liveData
import androidx.lifecycle.map
import com.aurora.gplayapi.Constants
import com.aurora.gplayapi.SearchSuggestEntry
import com.aurora.gplayapi.data.models.App
@@ -73,7 +72,6 @@ import kotlinx.coroutines.Deferred
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withTimeout
import retrofit2.Response
import timber.log.Timber
@@ -100,7 +98,7 @@ class FusedApiImpl @Inject constructor(
        private const val CATEGORY_TITLE_REPLACEABLE_CONJUNCTION = "&"
        private const val CATEGORY_OPEN_GAMES_ID = "game_open_games"
        private const val CATEGORY_OPEN_GAMES_TITLE = "Open games"
        private const val ERROR_GPLAY_SEARCH = "Gplay search has failed!"
        private const val ERROR_GPLAY_API = "Gplay api has faced error!"
        private const val ERROR_GPLAY_SOURCE_NOT_SELECTED = "Gplay apps are not selected!"
    }

@@ -167,29 +165,32 @@ class FusedApiImpl @Inject constructor(
        authData: AuthData,
    ): ResultSupreme<List<FusedHome>> {

        val apiStatus = when (source) {
            Source.GPLAY -> runCodeWithTimeout({
        val result = when (source) {
            Source.GPLAY -> handleResultFromAppSources<List<FusedHome>> {
                priorList.addAll(fetchGPlayHome(authData))
            })
                priorList
            }

            Source.OPEN -> runCodeWithTimeout({
            Source.OPEN -> handleResultFromAppSources {
                val response =
                    (cleanApkAppsRepository.getHomeScreenData() as Response<HomeScreen>).body()
                response?.home?.let {
                    priorList.addAll(generateCleanAPKHome(it, APP_TYPE_OPEN))
                }
            })
                priorList
            }

            Source.PWA -> runCodeWithTimeout({
            Source.PWA -> handleResultFromAppSources {
                val response =
                    (cleanApkPWARepository.getHomeScreenData() as Response<HomeScreen>).body()
                response?.home?.let {
                    priorList.addAll(generateCleanAPKHome(it, APP_TYPE_PWA))
                }
            })
                priorList
            }
        }

        setHomeErrorMessage(apiStatus, source)
        setHomeErrorMessage(result.getResultStatus(), source)
        priorList.sortByDescending {
            when (it.source) {
                APP_TYPE_OPEN -> 2
@@ -197,7 +198,7 @@ class FusedApiImpl @Inject constructor(
                else -> 3
            }
        }
        return ResultSupreme.create(apiStatus, priorList)
        return ResultSupreme.create(result.getResultStatus(), priorList)
    }

    private fun setHomeErrorMessage(apiStatus: ResultStatus, source: Source) {
@@ -323,16 +324,17 @@ class FusedApiImpl @Inject constructor(
        searchResult: MutableList<FusedApp>,
        packageSpecificResults: ArrayList<FusedApp>
    ): ResultSupreme<Pair<List<FusedApp>, Boolean>> {
        val status = runCodeWithTimeout({
        val result = handleResultFromAppSources {
            cleanApkResults.addAll(getCleanAPKSearchResults(query))
        })
            cleanApkResults
        }

        if (cleanApkResults.isNotEmpty()) {
            searchResult.addAll(cleanApkResults)
        }

        return ResultSupreme.create(
            status,
            result.getResultStatus(),
            Pair(
                filterWithKeywordSearch(
                    searchResult,
@@ -1083,12 +1085,12 @@ class FusedApiImpl @Inject constructor(
        query: String,
        nextPageSubBundle: Set<SearchBundle.SubBundle>?
    ): GplaySearchResult {
        try {
        return handleResultFromAppSources {
            val searchResults =
                gplayRepository.getSearchResult(query, nextPageSubBundle?.toMutableSet())

            if (!preferenceManagerModule.isGplaySelected()) {
                return ResultSupreme.Error(ERROR_GPLAY_SOURCE_NOT_SELECTED)
                return@handleResultFromAppSources Pair(listOf<FusedApp>(), setOf<SearchBundle.SubBundle>())
            }

            val fusedAppList =
@@ -1098,21 +1100,39 @@ class FusedApiImpl @Inject constructor(
                fusedAppList.add(FusedApp(isPlaceHolder = true))
            }

            return ResultSupreme.Success(Pair(fusedAppList.toList(), searchResults.second.toSet()))
        } catch (e: GplayHttpRequestException) {
            val message = (
                e.localizedMessage?.ifBlank { ERROR_GPLAY_SEARCH }
                    ?: ERROR_GPLAY_SEARCH
                ) + "Status: ${e.status}"
            return@handleResultFromAppSources Pair(fusedAppList.toList(), searchResults.second.toSet())
        }
    }

    private suspend fun <T> handleResultFromAppSources(call: suspend () -> T): ResultSupreme<T> {
        return try {
            ResultSupreme.Success(call())
        } catch (e: SocketTimeoutException) {
            val message = extractErrorMessage(e)
            val exception = GPlayException(true, message)
            val resultTimeout = ResultSupreme.Timeout<T>(exception = exception)
            resultTimeout.message = message

            resultTimeout
        } catch (e: GplayHttpRequestException) {
            val message = extractErrorMessage(e)
            val exception = GPlayException(e.status == 408, message)
            return ResultSupreme.Error(message, exception)

            ResultSupreme.Error(message, exception)
        } catch (e: Exception) {
            val exception =
                GPlayException(e is SocketTimeoutException, e.localizedMessage)
            val message = extractErrorMessage(e)
            ResultSupreme.Error(message, e)
        }
    }

            return ResultSupreme.Error(e.localizedMessage ?: "", exception)
    private fun extractErrorMessage(e: Exception): String {
        val status = when (e) {
            is GplayHttpRequestException -> e.status.toString()
            is SocketTimeoutException -> "Timeout"
            else -> "Unknown"
        }
        return (e.localizedMessage?.ifBlank { ERROR_GPLAY_API }
            ?: ERROR_GPLAY_API) + "Status: $status"
    }

    /*
+2 −24
Original line number Diff line number Diff line
@@ -54,7 +54,6 @@ class GPlayHttpClient @Inject constructor(
    companion object {
        private const val TAG = "GPlayHttpClient"
        private const val HTTP_TIMEOUT_IN_SECOND = 10L
        private const val SEARCH = "search"
    }

    private val okHttpClient = OkHttpClient().newBuilder()
@@ -159,26 +158,8 @@ class GPlayHttpClient @Inject constructor(
            val call = okHttpClient.newCall(request)
            buildPlayResponse(call.execute())
        } catch (e: Exception) {
            // TODO: exception will be thrown for all apis when all gplay api implementation
            // will handle the exceptions. this will be done in following issue.
            // Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/1483
            if (request.url.toString().contains(SEARCH)) {
            throw e
        }

            when (e) {
                is UnknownHostException,
                is SocketTimeoutException -> handleExceptionOnGooglePlayRequest(e)
                else -> handleExceptionOnGooglePlayRequest(e)
            }
        }
    }

    private fun handleExceptionOnGooglePlayRequest(e: Exception): PlayResponse {
        Timber.e("processRequest: ${e.localizedMessage}")
        return PlayResponse().apply {
            errorString = "${this@GPlayHttpClient::class.java.simpleName}: ${e.localizedMessage}"
        }
    }

    private fun buildUrl(url: String, params: Map<String, String>): HttpUrl {
@@ -203,10 +184,7 @@ class GPlayHttpClient @Inject constructor(
                }
            }

            // TODO: exception will be thrown for all apis when all gplay api implementation
            // will handle the exceptions. this will be done in following issue.
            // Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/1483
            if (response.request.url.toString().contains(SEARCH) && code != 200) {
            if (code != 200) {
                throw GplayHttpRequestException(code, response.message)
            }

+1 −4
Original line number Diff line number Diff line
@@ -144,7 +144,6 @@ class LoginApiRepository constructor(
     */
    private suspend fun <T> runCodeWithTimeout(
        block: suspend () -> T,
        timeoutBlock: (() -> T?)? = null,
        exceptionBlock: ((e: Exception) -> T?)? = null,
    ): ResultSupreme<T?> {
        return try {
@@ -152,9 +151,7 @@ class LoginApiRepository constructor(
                return@withTimeout ResultSupreme.Success(block())
            }
        } catch (e: TimeoutCancellationException) {
            ResultSupreme.Timeout(timeoutBlock?.invoke()).apply {
                message = e.message ?: ""
            }
            ResultSupreme.Timeout(exception = e)
        } catch (e: Exception) {
            e.printStackTrace()
            ResultSupreme.Error(exceptionBlock?.invoke(e), message = e.message ?: "")