package expo.modules.updates.procedures import android.app.Activity import android.content.Context import expo.modules.updates.UpdatesConfiguration import expo.modules.updates.db.DatabaseHolder import expo.modules.updates.db.Reaper import expo.modules.updates.launcher.DatabaseLauncher import expo.modules.updates.launcher.Launcher import expo.modules.updates.loader.FileDownloader import expo.modules.updates.logging.UpdatesErrorCode import expo.modules.updates.logging.UpdatesLogger import expo.modules.updates.reloadscreen.ReloadScreenManager import expo.modules.updates.selectionpolicy.SelectionPolicy import expo.modules.updates.statemachine.UpdatesStateEvent import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File import java.lang.ref.WeakReference class RelaunchProcedure( private val context: Context, private val weakActivity: WeakReference?, private val updatesConfiguration: UpdatesConfiguration, private val logger: UpdatesLogger, private val databaseHolder: DatabaseHolder, private val updatesDirectory: File, private val fileDownloader: FileDownloader, private val selectionPolicy: SelectionPolicy, private val getCurrentLauncher: () -> Launcher, private val setCurrentLauncher: (launcher: Launcher) -> Unit, private val shouldRunReaper: Boolean, private val reloadScreenManager: ReloadScreenManager?, private val callback: Launcher.LauncherCallback, private val procedureScope: CoroutineScope = CoroutineScope(Dispatchers.IO) ) : StateMachineProcedure() { override val loggerTimerLabel = "timer-relaunch" override suspend fun run(procedureContext: ProcedureContext) { val reactHost = resolveReactHostForRestart(context) ?: run inner@{ callback.onFailure(Exception("Could not reload application. Ensure you have passed the correct instance of ReactApplication into UpdatesController.initialize().")) return } procedureContext.processStateEvent(UpdatesStateEvent.Restart()) val newLauncher = DatabaseLauncher( context, updatesConfiguration, updatesDirectory, fileDownloader, selectionPolicy, logger, procedureScope ) try { launchWith(newLauncher) } catch (e: CancellationException) { throw e } catch (e: Exception) { logger.error("Error launching new launcher", e, UpdatesErrorCode.Unknown) callback.onFailure(e) procedureContext.onComplete() return } setCurrentLauncher(newLauncher) callback.onSuccess() procedureScope.launch { withContext(Dispatchers.Main) { reloadScreenManager?.show(weakActivity?.get()) reactHost.restart(weakActivity?.get(), "Restart from RelaunchProcedure") } } if (shouldRunReaper) { runReaper() } procedureContext.resetStateAfterRestart() procedureContext.onComplete() } private fun runReaper() { procedureScope.launch { try { Reaper.reapUnusedUpdates( updatesConfiguration, databaseHolder.database, updatesDirectory, getCurrentLauncher().launchedUpdate, selectionPolicy ) } catch (e: CancellationException) { throw e } catch (e: Exception) { logger.error("Could not run Reaper.", e, UpdatesErrorCode.Unknown) } } } private suspend fun launchWith(newLauncher: DatabaseLauncher) { newLauncher.launch(databaseHolder.database) } }