1 package expo.modules.updates.launcher
2 
3 import android.content.Context
4 import android.os.AsyncTask
5 import android.util.Log
6 import expo.modules.updates.UpdatesConfiguration
7 import expo.modules.updates.db.entity.AssetEntity
8 import expo.modules.updates.db.entity.UpdateEntity
9 import expo.modules.updates.loader.EmbeddedLoader
10 import expo.modules.updates.manifest.BareUpdateManifest
11 import expo.modules.updates.manifest.EmbeddedManifest
12 import org.apache.commons.io.FileUtils
13 import java.io.File
14 
15 /**
16  * Implementation of [Launcher] which always uses the update embedded in the application package,
17  * avoiding SQLite and the expo-updates file store entirely.
18  *
19  * This is only used in rare cases when the database/file system is corrupt or otherwise
20  * inaccessible, but we still want to avoid crashing. The exported property `isEmergencyLaunch`
21  * on [UpdatesModule] should be `true` whenever this class is used.
22  */
23 class NoDatabaseLauncher @JvmOverloads constructor(
24   context: Context,
25   configuration: UpdatesConfiguration,
26   fatalException: Exception? = null
27 ) : Launcher {
28   override var bundleAssetName: String? = null
29   override val launchedUpdate: UpdateEntity?
30     get() = null
31   override val launchAssetFile: String?
32     get() = null
33   override var localAssetFiles: Map<AssetEntity, String>? = null
34     private set
35   override val isUsingEmbeddedAssets: Boolean
36     get() = localAssetFiles == null
37 
writeErrorToLognull38   private fun writeErrorToLog(context: Context, fatalException: Exception) {
39     try {
40       val errorLogFile = File(context.filesDir, ERROR_LOG_FILENAME)
41       val exceptionString = fatalException.toString()
42       FileUtils.writeStringToFile(errorLogFile, exceptionString, "UTF-8", true)
43     } catch (e: Exception) {
44       Log.e(TAG, "Failed to write fatal error to log", e)
45     }
46   }
47 
48   companion object {
49     private val TAG = NoDatabaseLauncher::class.java.simpleName
50 
51     private const val ERROR_LOG_FILENAME = "expo-error.log"
52 
consumeErrorLognull53     fun consumeErrorLog(context: Context): String? {
54       return try {
55         val errorLogFile = File(context.filesDir, ERROR_LOG_FILENAME)
56         if (!errorLogFile.exists()) {
57           return null
58         }
59         val logContents = FileUtils.readFileToString(errorLogFile, "UTF-8")
60         errorLogFile.delete()
61         logContents
62       } catch (e: Exception) {
63         Log.e(TAG, "Failed to read error log", e)
64         null
65       }
66     }
67   }
68 
69   init {
70     val embeddedUpdateManifest = EmbeddedManifest.get(context, configuration)
71       ?: throw RuntimeException("Failed to launch with embedded update because the embedded manifest was null")
72 
73     if (embeddedUpdateManifest is BareUpdateManifest) {
74       bundleAssetName = EmbeddedLoader.BARE_BUNDLE_FILENAME
75       localAssetFiles = null
76     } else {
77       bundleAssetName = EmbeddedLoader.BUNDLE_FILENAME
<lambda>null78       localAssetFiles = mutableMapOf<AssetEntity, String>().apply {
79         for (asset in embeddedUpdateManifest.assetEntityList) {
80           this[asset] = "asset:///" + asset.embeddedAssetFilename
81         }
82       }
83     }
84 
85     if (fatalException != null) {
<lambda>null86       AsyncTask.execute { writeErrorToLog(context, fatalException) }
87     }
88   }
89 }
90