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

Commit a41d603c authored by dev-12's avatar dev-12 Committed by dev-12
Browse files

refactor: return update status and centralize retry logic

Make checkForUpdates return a ResultStatus so callers can act on success/failure, and replace the scattered retry/auth logic with a single getAvailableUpdates + loadWithRetry path that always returns a value.
parent f06a6b5d
Loading
Loading
Loading
Loading
+63 −75
Original line number Diff line number Diff line
@@ -11,7 +11,6 @@ import androidx.work.CoroutineWorker
import androidx.work.WorkInfo.State
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import com.aurora.gplayapi.data.models.AuthData
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import foundation.e.apps.R
@@ -109,89 +108,22 @@ class UpdatesWorker @AssistedInject constructor(
        return appLoungeDataStore.getLoginState()
    }

    private suspend fun checkForUpdates() {
    private suspend fun checkForUpdates(): ResultStatus {
        loadSettings()
        val isConnectedToUnMeteredNetwork = isConnectedToUnMeteredNetwork(applicationContext)
        val appsNeededToUpdate = mutableListOf<Application>()
        val user = getUser()
        val loginState = getLoginState()
        val authData = appLoungeDataStore.getAuthData()
        val resultStatus: ResultStatus
        val (appsNeededToUpdate, resultStatus) = loadWithRetry(emptyList(), ::getAvailableUpdates)

        if (user in listOf(User.ANONYMOUS, User.GOOGLE)) {
            /*
             * Signifies valid Google user and valid auth data to update
             * apps from Google Play store.
             * The user check will be more useful in No-Google mode.
             */
            val updateData = fetchUpdatesWithAuthRetry()
            appsNeededToUpdate.addAll(updateData.first)
            resultStatus = updateData.second
        } else if (loginState != LoginState.UNAVAILABLE) {
            /*
             * If authData is null, update apps from cleanapk only.
             */
            val updateData = updatesManagerRepository.getUpdatesOSS()
            appsNeededToUpdate.addAll(updateData.first)
            resultStatus = updateData.second
        } else {
            /*
             * If login state is unavailable, don't do anything.
             */
            Timber.e("Update is aborted for unavailable user!")
            return
        }
        Timber.i("Updates found: ${appsNeededToUpdate.size}; $resultStatus")
        if (isAutoUpdate && shouldShowNotification) {
            handleNotification(appsNeededToUpdate.size, isConnectedToUnMeteredNetwork)
        if (resultStatus != ResultStatus.OK) {
            return resultStatus
        }

        if (resultStatus != ResultStatus.OK) {
            manageRetry(resultStatus.toString())
        } else {
            /*
             * Show notification only if enabled.
             * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5376
             */
            retryCount = 0
        Timber.i("Updates found: ${appsNeededToUpdate.size}; $resultStatus")
        if (isAutoUpdate && shouldShowNotification) {
            handleNotification(appsNeededToUpdate.size, isConnectedToUnMeteredNetwork)
        }

            triggerUpdateProcessOnSettings(
                isConnectedToUnMeteredNetwork,
                appsNeededToUpdate,
                authData,
            )
        }
    }

    private suspend fun fetchUpdatesWithAuthRetry(): Pair<List<Application>, ResultStatus> {
        val updateData = updatesManagerRepository.getUpdates()
        if (updateData.second == ResultStatus.OK) {
            return updateData
        }

        val refreshedAuth = authenticatorRepository.getValidatedAuthData().data
        val shouldRetry = refreshedAuth != null

        return if (shouldRetry) updatesManagerRepository.getUpdates() else updateData
    }

    private suspend fun manageRetry(extraMessage: String) {
        retryCount++
        if (retryCount == 1) {
            EventBus.invokeEvent(AppEvent.UpdateEvent(ResultSupreme.WorkError(ResultStatus.RETRY)))
        }

        if (retryCount <= MAX_RETRY_COUNT) {
            delay(DELAY_FOR_RETRY)
            checkForUpdates()
        } else {
            val message = "Update is aborted after trying for $MAX_RETRY_COUNT times! message: $extraMessage"
            Timber.e(message)
            EventBus.invokeEvent(AppEvent.UpdateEvent(ResultSupreme.WorkError(ResultStatus.UNKNOWN)))
        }
        val jobsEnqueued = triggerUpdateProcessOnSettings(isConnectedToUnMeteredNetwork, appsNeededToUpdate)
        return if (jobsEnqueued.all { it.second }) ResultStatus.OK else ResultStatus.UNKNOWN
    }

    private suspend fun triggerUpdateProcessOnSettings(
@@ -213,6 +145,62 @@ class UpdatesWorker @AssistedInject constructor(
        }
    }

    private suspend fun getAvailableUpdates(): Pair<List<Application>, ResultStatus> {
        loadSettings()
        val appsNeededToUpdate = mutableListOf<Application>()
        val user = getUser()
        val authData = authenticatorRepository.getValidatedAuthData().data
        val resultStatus: ResultStatus

        // TODO: we should check if data is null and if in anonymous mode maybe refresh it
        //  and inform the user about this error if it fails.
        if (user in listOf(User.ANONYMOUS, User.GOOGLE) && authData != null) {
            /*
             * Signifies valid Google user and valid auth data to update
             * apps from Google Play store.
             * The user check will be more useful in No-Google mode.
             */
            val (apps, status) = updatesManagerRepository.getUpdates()
            appsNeededToUpdate.addAll(apps)
            resultStatus = status
        } else if (user == User.NO_GOOGLE) {
            /*
             * If authData is null, update apps from cleanapk only.
             */
            val (apps, status) = updatesManagerRepository.getUpdatesOSS()
            appsNeededToUpdate.addAll(apps)
            resultStatus = status
        } else {
            /*
             * If user in UNAVAILABLE, don't do anything.
             */
            Timber.e("Update is aborted for unavailable user!")
            resultStatus = ResultStatus.UNKNOWN
        }

        return Pair(appsNeededToUpdate, resultStatus)
    }

    private suspend fun <T> loadWithRetry(
        defaultValue: T,
        loadData: suspend () -> Pair<T, ResultStatus>
    ): Pair<T, ResultStatus> {
        while (retryCount <= MAX_RETRY_COUNT) {
            retryCount++
            val (data, status) = loadData()
            if (status == ResultStatus.OK) {
                return Pair(data, status)
            }
            delay(DELAY_FOR_RETRY)
            EventBus.invokeEvent(AppEvent.UpdateEvent(ResultSupreme.WorkError(ResultStatus.RETRY)))
        }

        val message = "Update is aborted after trying for $MAX_RETRY_COUNT times!"
        Timber.e(message)
        EventBus.invokeEvent(AppEvent.UpdateEvent(ResultSupreme.WorkError(ResultStatus.UNKNOWN)))
        return Pair(defaultValue, ResultStatus.UNKNOWN)
    }

    private fun handleNotification(
        numberOfAppsNeedUpdate: Int,
        isConnectedToUnmeteredNetwork: Boolean