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

Verified Commit 36b64557 authored by Fahim M. Choudhury's avatar Fahim M. Choudhury
Browse files

refactor: simplify install flow code

Flatten processInstall into a guard-clause flow and add a local ReturnCount suppression so the install preconditions and execution path are easier to read. This keeps the existing install behavior and failure handling intact while reducing nesting and temporary state.
parent b6c10bff
Loading
Loading
Loading
Loading
+38 −34
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import foundation.e.apps.data.ResultSupreme
import foundation.e.apps.data.application.ApplicationRepository
import foundation.e.apps.data.enums.ResultStatus
import foundation.e.apps.data.event.AppEvent
import foundation.e.apps.data.install.AppInstallRepository
import foundation.e.apps.data.install.AppManager
import foundation.e.apps.data.install.AppManagerWrapper
import foundation.e.apps.data.install.models.AppInstall
@@ -34,11 +35,12 @@ import javax.inject.Inject

class AppInstallDownloadUrlRefresher @Inject constructor(
    private val applicationRepository: ApplicationRepository,
    private val appInstallRepository: AppInstallRepository,
    private val appManagerWrapper: AppManagerWrapper,
    private val appEventDispatcher: AppEventDispatcher,
    private val appManager: AppManager,
) {
    suspend fun updateDownloadUrls(appInstall: AppInstall): Boolean {
    suspend fun updateDownloadUrls(appInstall: AppInstall, isAnUpdate: Boolean): Boolean {
        return runCatching {
            applicationRepository.updateFusedDownloadWithDownloadingInfo(
                appInstall.source,
@@ -46,34 +48,32 @@ class AppInstallDownloadUrlRefresher @Inject constructor(
            )
        }.fold(
            onSuccess = { true },
            onFailure = { throwable -> handleUpdateDownloadFailure(appInstall, throwable) }
        )
    }

    private suspend fun handleUpdateDownloadFailure(appInstall: AppInstall, throwable: Throwable): Boolean {
        return when (throwable) {
            is CancellationException -> throw throwable
            is InternalException.AppNotPurchased -> handleAppNotPurchased(appInstall)
            is GplayHttpRequestException -> {
                handleUpdateDownloadError(
            onFailure = { throwable ->
                handleUpdateDownloadFailure(
                    appInstall,
                    "${appInstall.packageName} code: ${throwable.status} exception: ${throwable.localizedMessage}",
                    isAnUpdate,
                    throwable
                )
                false
            }

            is IllegalStateException -> {
                Timber.e(throwable)
                false
        )
    }

    private suspend fun handleUpdateDownloadFailure(
        appInstall: AppInstall,
        isAnUpdate: Boolean,
        throwable: Throwable
    ): Boolean {
        return when (throwable) {
            is CancellationException -> throw throwable
            is InternalException.AppNotPurchased -> handleAppNotPurchased(appInstall)
            is Exception -> {
                handleUpdateDownloadError(
                    appInstall,
                    "${appInstall.packageName} exception: ${throwable.localizedMessage}",
                    throwable
                )
                val message = if (throwable is GplayHttpRequestException) {
                    "${appInstall.packageName} code: ${throwable.status} exception: ${throwable.message}"
                } else {
                    "${appInstall.packageName} exception: ${throwable.message}"
                }
                Timber.e(throwable, "Updating download URLS failed for $message")
                handleUpdateDownloadError(appInstall, isAnUpdate)
                false
            }

@@ -93,12 +93,15 @@ class AppInstallDownloadUrlRefresher @Inject constructor(
        return false
    }

    private suspend fun handleUpdateDownloadError(
        appInstall: AppInstall,
        message: String,
        exception: Exception
    ) {
        Timber.e(exception, "Updating download Urls failed for $message")
    private suspend fun handleUpdateDownloadError(appInstall: AppInstall, isAnUpdate: Boolean) {
        // Insert into DB to reflect error state on UI.
        // For example, install button's label will change to Install -> Cancel -> Retry
        if (appInstallRepository.getDownloadById(appInstall.id) == null) {
            appInstallRepository.addDownload(appInstall)
        }
        appManagerWrapper.installationIssue(appInstall)

        if (isAnUpdate) {
            appEventDispatcher.dispatch(
                AppEvent.UpdateEvent(
                    ResultSupreme.WorkError(
@@ -109,3 +112,4 @@ class AppInstallDownloadUrlRefresher @Inject constructor(
            )
        }
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -30,9 +30,9 @@ class AppInstallPreEnqueueChecker @Inject constructor(
    private val appInstallAgeLimitGate: AppInstallAgeLimitGate,
    private val appInstallDevicePreconditions: AppInstallDevicePreconditions,
) {
    suspend fun canEnqueue(appInstall: AppInstall): Boolean {
    suspend fun canEnqueue(appInstall: AppInstall, isAnUpdate: Boolean = false): Boolean {
        val hasUpdatedDownloadUrls = appInstall.type == Type.PWA ||
            appInstallDownloadUrlRefresher.updateDownloadUrls(appInstall)
            appInstallDownloadUrlRefresher.updateDownloadUrls(appInstall, isAnUpdate)

        val isDownloadAdded = hasUpdatedDownloadUrls && addDownload(appInstall)
        val isAgeLimitAllowed = isDownloadAdded && appInstallAgeLimitGate.allow(appInstall)
+5 −3
Original line number Diff line number Diff line
@@ -48,7 +48,7 @@ class AppInstallStartCoordinator @Inject constructor(
        return runCatching {
            dispatchAnonymousPaidAppWarning(appInstall, isSystemApp)

            val canEnqueue = canEnqueue(appInstall)
            val canEnqueue = canEnqueue(appInstall, isAnUpdate)
            if (canEnqueue) {
                appManagerWrapper.updateAwaiting(appInstall)

@@ -58,6 +58,8 @@ class AppInstallStartCoordinator @Inject constructor(
                    InstallWorkManager.enqueueWork(context, appInstall, true)
                    Timber.d("UPDATE: Successfully enqueued unique work: $uniqueWorkName")
                }
            } else {
                Timber.w("Can't enqueue ${appInstall.name}/${appInstall.packageName} for installation.")
            }

            canEnqueue
@@ -78,8 +80,8 @@ class AppInstallStartCoordinator @Inject constructor(
        }
    }

    suspend fun canEnqueue(appInstall: AppInstall): Boolean {
        return appInstallPreEnqueueChecker.canEnqueue(appInstall)
    suspend fun canEnqueue(appInstall: AppInstall, isAnUpdate: Boolean = false): Boolean {
        return appInstallPreEnqueueChecker.canEnqueue(appInstall, isAnUpdate)
    }

    private suspend fun dispatchAnonymousPaidAppWarning(appInstall: AppInstall, isSystemApp: Boolean) {
+39 −41
Original line number Diff line number Diff line
@@ -36,67 +36,65 @@ class AppInstallWorkRunner @Inject constructor(
    private val downloadManager: DownloadManagerUtils,
    private val appUpdateCompletionHandler: AppUpdateCompletionHandler,
) {
    @Suppress("ReturnCount")
    @OptIn(DelicateCoroutinesApi::class)
    suspend fun processInstall(
        fusedDownloadId: String,
        isItUpdateWork: Boolean,
        runInForeground: suspend (String) -> Unit
    ): Result<ResultStatus> {
        var appInstall: AppInstall? = null
        runCatching {
            Timber.d("Fused download name $fusedDownloadId")
        val appInstall =
            appInstallRepository.getDownloadById(fusedDownloadId) ?: return Result.failure(
                IllegalStateException("App can't be null here.")
            )

            appInstall = appInstallRepository.getDownloadById(fusedDownloadId)
            Timber.i(">>> doWork started for Fused download name ${appInstall?.name} $fusedDownloadId")
        Timber.i(">>> doWork() started for ${appInstall.name}/${appInstall.packageName}")

            appInstall?.let {
                checkDownloadingState(it)
        checkDownloadingState(appInstall)

                val isUpdateWork =
                    isItUpdateWork && appManagerWrapper.isFusedDownloadInstalled(it)
        if (!appInstall.isAppInstalling()) {
            val message = "${appInstall.status} is in invalid state"
            Timber.w(message)

                if (!it.isAppInstalling()) {
                    Timber.d("!!! returned")
                    return@let
            return Result.failure(IllegalStateException(message))
        }

                if (!appManagerWrapper.validateFusedDownload(it)) {
                    appManagerWrapper.installationIssue(it)
                    Timber.d("!!! installationIssue")
                    return@let
        if (!appManagerWrapper.validateFusedDownload(appInstall)) {
            appManagerWrapper.installationIssue(appInstall)
            val message = "Installation issue for ${appInstall.name}/${appInstall.packageName}"
            Timber.w(message)

            return Result.failure(IllegalStateException(message))
        }

                if (areFilesDownloadedButNotInstalled(it)) {
                    Timber.i("===> Downloaded But not installed ${it.name}")
                    appManagerWrapper.updateDownloadStatus(it, Status.INSTALLING)
        return runCatching {
            val isUpdateWork =
                isItUpdateWork && appManagerWrapper.isFusedDownloadInstalled(appInstall)

            if (areFilesDownloadedButNotInstalled(appInstall)) {
                Timber.i("===> Downloaded But not installed ${appInstall.name}")
                appManagerWrapper.updateDownloadStatus(appInstall, Status.INSTALLING)
            }

                runInForeground.invoke(it.name)
            runInForeground.invoke(appInstall.name)
            startAppInstallationProcess(appInstall, isUpdateWork)
            Timber.i("doWork: RESULT SUCCESS: ${appInstall.name}")

                startAppInstallationProcess(it, isUpdateWork)
            ResultStatus.OK
        }.onFailure { exception ->
            if (exception is CancellationException) {
                throw exception
            }
        }.onFailure { throwable ->
            when (throwable) {
                is CancellationException -> throw throwable
                is Exception -> {

            Timber.e(
                        throwable,
                        "Install worker is failed for ${appInstall?.packageName} " +
                            "exception: ${throwable.localizedMessage}"
                exception,
                "Install worker failed for ${appInstall.packageName} exception: ${exception.message}"
            )
                    appInstall?.let {
                        appManagerWrapper.cancelDownload(it)
                    }
                }

                else -> throw throwable
            appManagerWrapper.cancelDownload(appInstall)
        }
    }

        Timber.i("doWork: RESULT SUCCESS: ${appInstall?.name}")
        return Result.success(ResultStatus.OK)
    }

    @OptIn(DelicateCoroutinesApi::class)
    private fun checkDownloadingState(appInstall: AppInstall) {
        if (appInstall.status == Status.DOWNLOADING) {
+11 −6
Original line number Diff line number Diff line
@@ -59,15 +59,20 @@ class InstallAppWorker @AssistedInject constructor(

    override suspend fun doWork(): Result {
        val fusedDownloadId = params.inputData.getString(INPUT_DATA_FUSED_DOWNLOAD) ?: ""
        if (fusedDownloadId.isEmpty()) {
            return Result.failure()
        }

        val isPackageUpdate = params.inputData.getBoolean(IS_UPDATE_WORK, false)
        val response = appInstallProcessor.processInstall(fusedDownloadId, isPackageUpdate) { title ->
            setForeground(
                createForegroundInfo(
                    "${context.getString(R.string.installing)} $title"
                )
            )
            setForeground(createForegroundInfo("${context.getString(R.string.installing)} $title"))
        }

        return if (response.isSuccess) {
            Result.success()
        } else {
            Result.failure()
        }
        return if (response.isSuccess) Result.success() else Result.failure()
    }

    private fun createForegroundInfo(progress: String): ForegroundInfo {
Loading