<lambda>null1 package expo.modules.updates.loader
2 
3 import android.content.Context
4 import android.util.Log
5 import expo.modules.updates.UpdatesConfiguration
6 import expo.modules.updates.db.UpdatesDatabase
7 import expo.modules.updates.db.entity.AssetEntity
8 import expo.modules.updates.db.entity.UpdateEntity
9 import expo.modules.updates.loader.FileDownloader.AssetDownloadCallback
10 import expo.modules.updates.loader.FileDownloader.RemoteUpdateDownloadCallback
11 import expo.modules.updates.manifest.EmbeddedManifest
12 import expo.modules.updates.manifest.ManifestMetadata
13 import expo.modules.updates.selectionpolicy.SelectionPolicy
14 import java.io.File
15 
16 /**
17  * Subclass of [Loader] which handles downloading updates from a remote server.
18  *
19  * Unlike [EmbeddedLoader], it needs to manage file downloading. Currently, it does not skip
20  * any assets, meaning all assets must be downloaded in order for the update to be considered
21  * ready to launch.
22  */
23 class RemoteLoader internal constructor(
24   context: Context,
25   configuration: UpdatesConfiguration,
26   database: UpdatesDatabase,
27   private val mFileDownloader: FileDownloader,
28   updatesDirectory: File?,
29   private val launchedUpdate: UpdateEntity?,
30   private val loaderFiles: LoaderFiles
31 ) : Loader(context, configuration, database, updatesDirectory, loaderFiles) {
32   constructor(
33     context: Context,
34     configuration: UpdatesConfiguration,
35     database: UpdatesDatabase,
36     fileDownloader: FileDownloader,
37     updatesDirectory: File?,
38     launchedUpdate: UpdateEntity?
39   ) : this(context, configuration, database, fileDownloader, updatesDirectory, launchedUpdate, LoaderFiles())
40 
41   override fun loadRemoteUpdate(
42     context: Context,
43     database: UpdatesDatabase,
44     configuration: UpdatesConfiguration,
45     callback: RemoteUpdateDownloadCallback
46   ) {
47     val embeddedUpdate = loaderFiles.readEmbeddedManifest(context, configuration)?.updateEntity
48     val extraHeaders = FileDownloader.getExtraHeadersForRemoteUpdateRequest(database, configuration, launchedUpdate, embeddedUpdate)
49     mFileDownloader.downloadRemoteUpdate(configuration, extraHeaders, context, callback)
50   }
51 
52   override fun loadAsset(
53     context: Context,
54     assetEntity: AssetEntity,
55     updatesDirectory: File?,
56     configuration: UpdatesConfiguration,
57     callback: AssetDownloadCallback
58   ) {
59     mFileDownloader.downloadAsset(assetEntity, updatesDirectory, configuration, context, callback)
60   }
61 
62   override fun shouldSkipAsset(assetEntity: AssetEntity): Boolean {
63     return false
64   }
65 
66   companion object {
67     private val TAG = RemoteLoader::class.java.simpleName
68 
69     fun processSuccessLoaderResult(
70       context: Context,
71       configuration: UpdatesConfiguration,
72       database: UpdatesDatabase,
73       selectionPolicy: SelectionPolicy,
74       directory: File?,
75       launchedUpdate: UpdateEntity?,
76       loaderResult: LoaderResult,
77       onComplete: (availableUpdate: UpdateEntity?, didRollBackToEmbedded: Boolean) -> Unit
78     ) {
79       val updateEntity = loaderResult.updateEntity
80       val updateDirective = loaderResult.updateDirective
81 
82       if (updateDirective != null && updateDirective is UpdateDirective.RollBackToEmbeddedUpdateDirective) {
83         processRollBackToEmbeddedDirective(context, configuration, database, selectionPolicy, directory, launchedUpdate, updateDirective) { didRollBackToEmbedded ->
84           onComplete(null, didRollBackToEmbedded)
85         }
86       } else {
87         onComplete(updateEntity, false)
88       }
89     }
90 
91     /**
92      * If directive is to roll-back to the embedded update and there is an embedded update,
93      * we need to update embedded update in the DB with the newer commitTime from the directive
94      * so that the selection policy will choose it. That way future updates can continue to be applied
95      * over this roll back, but older ones won't.
96      */
97     private fun processRollBackToEmbeddedDirective(
98       context: Context,
99       configuration: UpdatesConfiguration,
100       database: UpdatesDatabase,
101       selectionPolicy: SelectionPolicy,
102       directory: File?,
103       launchedUpdate: UpdateEntity?,
104       updateDirective: UpdateDirective.RollBackToEmbeddedUpdateDirective,
105       onComplete: (didRollBackToEmbedded: Boolean) -> Unit
106     ) {
107       if (!configuration.hasEmbeddedUpdate) {
108         onComplete(false)
109         return
110       }
111 
112       val embeddedUpdate = EmbeddedManifest.get(context, configuration)!!.updateEntity
113       if (embeddedUpdate == null) {
114         onComplete(false)
115         return
116       }
117 
118       val manifestFilters = ManifestMetadata.getManifestFilters(database, configuration)
119       if (!selectionPolicy.shouldLoadRollBackToEmbeddedDirective(updateDirective, embeddedUpdate, launchedUpdate, manifestFilters)) {
120         onComplete(false)
121         return
122       }
123 
124       // update the embedded update commit time in the in-memory embedded update since it is a singleton
125       embeddedUpdate.commitTime = updateDirective.commitTime
126 
127       // update the embedded update commit time in the database (requires loading and then updating)
128       EmbeddedLoader(context, configuration, database, directory).start(object : LoaderCallback {
129         /**
130          * This should never happen since we check for the embedded update above
131          */
132         override fun onFailure(e: Exception) {
133           Log.e(TAG, "Embedded update erroneously null when applying roll back to embedded directive", e)
134           onComplete(false)
135         }
136 
137         override fun onSuccess(loaderResult: Loader.LoaderResult) {
138           val embeddedUpdateToLoad = loaderResult.updateEntity
139           database.updateDao().setUpdateCommitTime(embeddedUpdateToLoad!!, updateDirective.commitTime)
140           onComplete(true)
141         }
142 
143         override fun onAssetLoaded(
144           asset: AssetEntity,
145           successfulAssetCount: Int,
146           failedAssetCount: Int,
147           totalAssetCount: Int
148         ) {
149         }
150 
151         override fun onUpdateResponseLoaded(updateResponse: UpdateResponse): OnUpdateResponseLoadedResult {
152           return OnUpdateResponseLoadedResult(shouldDownloadManifestIfPresentInResponse = true)
153         }
154       })
155     }
156   }
157 }
158