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

Commit 10937de0 authored by Jonathan Klee's avatar Jonathan Klee
Browse files

Merge branch '0000-a15-fix-sentry-ai-review' into 'main'

Fix sentry crash

See merge request !731
parents b07033bb 01f50790
Loading
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -27,9 +27,11 @@ import foundation.e.apps.data.playstore.utils.GplayHttpRequestException
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.delay
import timber.log.Timber
import java.io.InterruptedIOException
import java.net.SocketTimeoutException

private const val TIMEOUT = "Timeout"
private val TIMEOUT_MESSAGE_PATTERNS = listOf("timeout", "timed out")
private const val UNKNOWN = "Unknown"
private const val STATUS = "Status:"
private const val ERROR_GPLAY_API = "Gplay api has faced error!"
@@ -49,6 +51,8 @@ suspend fun <T> handleNetworkResult(call: suspend () -> T): ResultSupreme<T> {
        throw e
    } catch (e: SocketTimeoutException) {
        handleSocketTimeoutException(e)
    } catch (e: InterruptedIOException) {
        handleInterruptedIOException(e)
    } catch (e: GplayHttpRequestException) {
        resultSupremeGplayHttpRequestException(e)
    } catch (e: Exception) {
@@ -63,6 +67,16 @@ private fun <T> handleSocketTimeoutException(e: SocketTimeoutException): ResultS
    return resultTimeout
}

private fun <T> handleInterruptedIOException(e: InterruptedIOException): ResultSupreme<T> {
    return if (isTimeoutInterruptedIOException(e)) {
        val resultTimeout = ResultSupreme.Timeout<T>(exception = e)
        resultTimeout.message = extractErrorMessage(e)
        resultTimeout
    } else {
        handleOthersException(e)
    }
}

private fun <T> resultSupremeGplayHttpRequestException(e: GplayHttpRequestException): ResultSupreme<T> {
    val message = extractErrorMessage(e)
    val exception = GPlayException(e.status == GPlayHttpClient.STATUS_CODE_TIMEOUT, message)
@@ -83,12 +97,20 @@ private fun extractErrorMessage(e: Exception): String {
    val status = when (e) {
        is GplayHttpRequestException -> e.status.toString()
        is SocketTimeoutException -> TIMEOUT
        is InterruptedIOException -> if (isTimeoutInterruptedIOException(e)) TIMEOUT else UNKNOWN
        else -> UNKNOWN
    }

    return (e.localizedMessage?.ifBlank { ERROR_GPLAY_API } ?: ERROR_GPLAY_API) + " $STATUS $status"
}

private fun isTimeoutInterruptedIOException(e: InterruptedIOException): Boolean {
    val message = e.message ?: return true
    return TIMEOUT_MESSAGE_PATTERNS.any { pattern ->
        message.contains(pattern, ignoreCase = true)
    }
}

suspend fun <T> retryWithBackoff(retryDelayInSeconds: Int = -1, operation: suspend () -> T): T? {
    var result: T? = null
    try {
+20 −4
Original line number Diff line number Diff line
@@ -5,7 +5,12 @@ import foundation.e.apps.data.application.data.Application
import foundation.e.apps.data.cleanapk.ApkSignatureManager
import foundation.e.apps.data.enums.Source
import foundation.e.apps.data.fdroid.models.BuildInfo
import foundation.e.apps.data.fdroid.models.FdroidApiModel
import foundation.e.apps.data.fdroid.models.FdroidEntity
import kotlinx.coroutines.CancellationException
import retrofit2.Response
import timber.log.Timber
import java.io.IOException
import javax.inject.Inject
import javax.inject.Singleton

@@ -29,15 +34,15 @@ class FDroidRepository @Inject constructor(
     */
    override suspend fun getFdroidInfo(packageName: String): FdroidEntity? {
        return fdroidDao.getFdroidEntityFromPackageName(packageName)
            ?: fdroidApi.getFdroidInfoForPackage(packageName).body()?.let {
            ?: getFdroidApiResponse(packageName)?.body()?.let {
                FdroidEntity(packageName, it.authorName).also {
                    fdroidDao.saveFdroidEntity(it)
                }
            }
    }

    suspend fun getBuildVersionInfo(packageName: String): List<BuildInfo>? {
        return fdroidApi.getFdroidInfoForPackage(packageName).body()?.builds
    suspend fun getBuildVersionInfo(packageName: String): List<BuildInfo> {
        return getFdroidApiResponse(packageName)?.body()?.builds ?: emptyList()
    }

    override suspend fun getAuthorName(application: Application): String {
@@ -69,6 +74,17 @@ class FDroidRepository @Inject constructor(
    }

    override suspend fun isFdroidApplication(packageName: String): Boolean {
        return fdroidApi.getFdroidInfoForPackage(packageName).isSuccessful
        return getFdroidApiResponse(packageName)?.isSuccessful == true
    }

    private suspend fun getFdroidApiResponse(packageName: String): Response<FdroidApiModel>? {
        return try {
            fdroidApi.getFdroidInfoForPackage(packageName)
        } catch (exception: CancellationException) {
            throw exception
        } catch (exception: IOException) {
            Timber.w(exception, "Failed to fetch F-Droid metadata for %s", packageName)
            null
        }
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -34,5 +34,5 @@ interface FdroidApiInterface {
    }

    @GET("{packageName}.yml")
    suspend fun getFdroidInfoForPackage(@Path("packageName") packageName: String): Response<FdroidApiModel?>
    suspend fun getFdroidInfoForPackage(@Path("packageName") packageName: String): Response<FdroidApiModel>
}
+1 −1
Original line number Diff line number Diff line
@@ -389,7 +389,7 @@ class UpdatesManagerImpl @Inject constructor(
        // Received list has build info of the latest version at the bottom.
        // We want it at the top.
        val builds = handleNetworkResult {
            fDroidRepository.getBuildVersionInfo(packageName)?.asReversed() ?: listOf()
            fDroidRepository.getBuildVersionInfo(packageName).asReversed()
        }.data

        val matchingIndex = builds?.find {
+34 −0
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ import foundation.e.apps.data.playstore.utils.GplayHttpRequestException
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
import java.io.InterruptedIOException
import java.net.SocketTimeoutException

@OptIn(ExperimentalCoroutinesApi::class)
@@ -33,6 +34,39 @@ class NetworkHandlerTest {
        assertThat(result.exception).isSameInstanceAs(timeout)
    }

    @Test
    fun handleNetworkResult_convertsInterruptedTimeout() = runTest {
        val timeout = InterruptedIOException("timeout")

        val result = handleNetworkResult<String> { throw timeout }

        assertThat(result.isTimeout()).isTrue()
        assertThat(result.message).isEqualTo("timeout Status: Timeout")
        assertThat(result.exception).isSameInstanceAs(timeout)
    }

    @Test
    fun handleNetworkResult_convertsInterruptedTimedOutMessage() = runTest {
        val timeout = InterruptedIOException("Read timed out")

        val result = handleNetworkResult<String> { throw timeout }

        assertThat(result.isTimeout()).isTrue()
        assertThat(result.message).isEqualTo("Read timed out Status: Timeout")
        assertThat(result.exception).isSameInstanceAs(timeout)
    }

    @Test
    fun handleNetworkResult_mapsNonTimeoutInterruptedIoExceptionToError() = runTest {
        val interrupted = InterruptedIOException("stream closed")

        val result = handleNetworkResult<String> { throw interrupted }

        assertThat(result.isUnknownError()).isTrue()
        assertThat(result.message).isEqualTo("stream closed Status: Unknown")
        assertThat(result.exception).isSameInstanceAs(interrupted)
    }

    @Test
    fun handleNetworkResult_mapsHttp429ToError() = runTest {
        val exception = GplayHttpRequestException(
Loading