From adeee346e4ba8f9aa4b9c950f76f25a73ecfee79 Mon Sep 17 00:00:00 2001
From: Shubham Goyal <shubham1g5@gmail.com>
Date: Thu, 5 Dec 2024 16:09:35 +0530
Subject: [PATCH] separate loading of entities from references into a method

---
 .../org/commcare/tasks/EntityLoaderHelper.kt  | 21 ++++--
 .../org/commcare/tasks/PrimeEntityCache.kt    | 24 ++++++
 .../commcare/tasks/PrimeEntityCacheHelper.kt  | 74 +++++++++++++++++++
 3 files changed, 114 insertions(+), 5 deletions(-)
 create mode 100644 app/src/org/commcare/tasks/PrimeEntityCache.kt
 create mode 100644 app/src/org/commcare/tasks/PrimeEntityCacheHelper.kt

diff --git a/app/src/org/commcare/tasks/EntityLoaderHelper.kt b/app/src/org/commcare/tasks/EntityLoaderHelper.kt
index 35cd11c99..e55c75354 100644
--- a/app/src/org/commcare/tasks/EntityLoaderHelper.kt
+++ b/app/src/org/commcare/tasks/EntityLoaderHelper.kt
@@ -35,8 +35,22 @@ class EntityLoaderHelper(
         }
     }
 
-    fun loadEntities(nodeset: TreeReference): Pair<List<Entity<TreeReference>>, List<TreeReference>>? {
+    /**
+     * Loads and prepares a list of entities derived from the given nodeset
+     */
+    fun loadEntities(nodeset: TreeReference): Pair<List<Entity<TreeReference>>, List<TreeReference>> {
         val references = factory.expandReferenceList(nodeset)
+        val entities = loadEntitiesWithReferences(references)
+        factory.prepareEntities(entities)
+        factory.printAndClearTraces("build")
+        return Pair<List<Entity<TreeReference>>, List<TreeReference>>(entities, references)
+    }
+
+
+    /**
+     * Loads a list of entities corresponding to the given references
+     */
+    private fun loadEntitiesWithReferences(references: List<TreeReference>): MutableList<Entity<TreeReference>>? {
         val entities: MutableList<Entity<TreeReference>> = ArrayList()
         focusTargetIndex = -1
         var indexInFullList = 0
@@ -53,10 +67,7 @@ class EntityLoaderHelper(
                 indexInFullList++
             }
         }
-
-        factory.prepareEntities(entities)
-        factory.printAndClearTraces("build")
-        return Pair<List<Entity<TreeReference>>, List<TreeReference>>(entities, references)
+        return entities
     }
 
     override fun cancel() {
diff --git a/app/src/org/commcare/tasks/PrimeEntityCache.kt b/app/src/org/commcare/tasks/PrimeEntityCache.kt
new file mode 100644
index 000000000..8c8bb72b5
--- /dev/null
+++ b/app/src/org/commcare/tasks/PrimeEntityCache.kt
@@ -0,0 +1,24 @@
+package org.commcare.tasks
+
+import android.content.Context
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import org.commcare.CommCareApplication
+import org.commcare.utils.SessionUnavailableException
+
+class PrimeEntityCache(appContext: Context, workerParams: WorkerParameters)
+    : Worker(appContext, workerParams)  {
+
+    override fun doWork(): Result {
+        try {
+            PrimeEntityCacheHelper.getInstance().primeEntityCache()
+            return Result.success();
+        } catch (_: SessionUnavailableException) {
+        }
+        return Result.failure()
+    }
+
+    override fun onStopped() {
+        PrimeEntityCacheHelper.getInstance().cancel()
+    }
+}
diff --git a/app/src/org/commcare/tasks/PrimeEntityCacheHelper.kt b/app/src/org/commcare/tasks/PrimeEntityCacheHelper.kt
new file mode 100644
index 000000000..4065f957e
--- /dev/null
+++ b/app/src/org/commcare/tasks/PrimeEntityCacheHelper.kt
@@ -0,0 +1,74 @@
+package org.commcare.tasks
+
+import io.reactivex.functions.Cancellable
+import okhttp3.internal.notifyAll
+import org.commcare.CommCareApplication
+import org.commcare.suite.model.Detail
+import org.commcare.suite.model.EntityDatum
+import org.commcare.utils.AndroidCommCarePlatform
+import org.javarosa.core.model.condition.EvaluationContext
+
+class PrimeEntityCacheHelper private constructor() : Cancellable {
+
+    private var entityLoaderHelper: EntityLoaderHelper? = null
+    private var inProgress = false
+
+    companion object {
+        @Volatile
+        private var instance: PrimeEntityCacheHelper? = null
+
+        fun getInstance() =
+            instance ?: synchronized(this) {
+                instance ?: PrimeEntityCacheHelper().also { instance = it }
+            }
+    }
+
+    fun primeEntityCache() {
+        checkPreConditions()
+        primeEntityCacheForApp(CommCareApplication.instance().commCarePlatform)
+        clearState()
+    }
+
+    private fun primeEntityCacheForApp(commCarePlatform: AndroidCommCarePlatform) {
+        inProgress = true
+        val commandMap = commCarePlatform.commandToEntryMap
+        for (command in commandMap.keys()) {
+            val entry = commandMap[command]!!
+            val sessionDatums = entry.sessionDataReqs
+            for (sessionDatum in sessionDatums) {
+                if (sessionDatum is EntityDatum) {
+                    val shortDetailId = sessionDatum.shortDetail
+                    if (shortDetailId != null) {
+                        val detail = commCarePlatform.getDetail(shortDetailId)
+                        primeCacheForDetail(detail, sessionDatum)
+                    }
+                }
+            }
+        }
+    }
+
+    private fun primeCacheForDetail(detail: Detail, sessionDatum: EntityDatum) {
+        if (detail.shouldCache()) {
+            entityLoaderHelper = EntityLoaderHelper(detail, evalCtx())
+            entityLoaderHelper!!.loadEntities(sessionDatum.nodeset)
+        }
+    }
+
+    private fun evalCtx(): EvaluationContext {
+        return CommCareApplication.instance().currentSessionWrapper.evaluationContext
+    }
+
+    private fun clearState() {
+        entityLoaderHelper = null
+        inProgress = false
+    }
+
+    private fun checkPreConditions() {
+        require(CommCareApplication.instance().session.isActive) { "User session must be active to prime entity cache" }
+        require(!inProgress) { "We are already priming the cache" }
+    }
+
+    override fun cancel() {
+        entityLoaderHelper?.cancel()
+    }
+}