Move SSD stale load checks to typed Kotlin

ober

a988ed70ccb2d9150fec116696e84920dab5f1b3

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index c80f53c..d4655f2 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -1647,7 +1647,7 @@
                 storageStatsExceedsQuota storageCapacityWithinQuota
                 byteCountWithinLimit storageEntryBytesAllowed
                 boundedReadLimitValid
-                positiveCount positiveByteCount learnedExamplesFull
+                positiveCount nonPositiveCount positiveByteCount learnedExamplesFull
                 streamReadEnded streamReadEmpty streamByteWasRead
                 inputCountExceeded outputSingleWriteExceeds
                 outputBufferWriteExceeds
@@ -1768,6 +1768,8 @@
           (and (>= limit (int 1)) (<= limit (int 2147483647))))
         (def (positiveCount (count : Int32)) : Bool
           (> count (int32 0)))
+        (def (nonPositiveCount (count : Int32)) : Bool
+          (not (positiveCount count)))
         (def (positiveByteCount (count : Int)) : Bool
           (> count (int 0)))
         (def (streamReadEnded (read : Int32)) : Bool
@@ -4218,7 +4220,7 @@
                 shouldPersistLoadedPdfState viewportScalePreferencePresent
                 lastPdfUriMismatch lastViewportPageMismatch
                 displayNameColumnPresent displayNameCursorHasFirstRow
-                currentLoadMatches
+                currentLoadMatches currentLoadStale
                 packageUriText mediaStorePdfSelection
                 mediaStoreDisplayNameSort
                 shouldSkipSsdPdfChoiceName
@@ -4740,6 +4742,19 @@
                (and (not (nullable-null? currentUri))
                     (and (equal? (nullable-get currentUri) expectedUri)
                          (= currentPage expectedPage)))))
+        (def (currentLoadStale (expectedGeneration : Int32)
+                               (loadGeneration : Int32)
+                               (currentUri : (Nullable Uri))
+                               (expectedUri : Uri)
+                               (currentPage : Int32)
+                               (expectedPage : Int32)) : Bool
+          (not (currentLoadMatches
+                 expectedGeneration
+                 loadGeneration
+                 currentUri
+                 expectedUri
+                 currentPage
+                 expectedPage)))
         (def (packageUriText (packageName : String)) : String
           (string-append "package:" packageName))
         (def (mediaStorePdfSelection (mimeTypeColumn : String)
@@ -6443,7 +6458,7 @@
        "            try {"
        "                val bitmap = renderPdfPage(uri, renderPageIndex)"
        "                runOnUiThread {"
-       "                    if (!isCurrentLoad(renderGeneration, uri, renderPageIndex)) return@runOnUiThread"
+       "                    if (isStaleLoad(renderGeneration, uri, renderPageIndex)) return@runOnUiThread"
        "                    currentBitmap = bitmap"
        "                    currentSession = null"
        "                    selectedGroupId = null"
@@ -6486,7 +6501,7 @@
        "        setStatus(detectStartStatus(forceDetect))"
        "        thread {"
        "            try {"
-       "                if (!isCurrentLoad(expectedGeneration, uri, expectedPageIndex)) return@thread"
+       "                if (isStaleLoad(expectedGeneration, uri, expectedPageIndex)) return@thread"
        "                val key = sourceKey(expectedPdfSha1, expectedPageIndex + 1, DPI)"
        "                val session = SsdSession("
        "                    sessionId = sessionIdFromUuid(UUID.randomUUID().toString()),"
@@ -6511,7 +6526,7 @@
 	       "                    truthStore.applyTruth(session)"
 	      "                    ssdSessionPruneGroupsOutsideSsdArea(session)"
 	       "                    runOnUiThread {"
-       "                        if (!isCurrentLoad(expectedGeneration, uri, expectedPageIndex)) return@runOnUiThread"
+       "                        if (isStaleLoad(expectedGeneration, uri, expectedPageIndex)) return@runOnUiThread"
        "                        currentSession = session"
        "                        selectedGroupId = null"
 	       "                        reviewView.session = session"
@@ -6562,7 +6577,7 @@
        "                }"
        "                val learnedGuesses = if (shouldApplyLearnedGuesses(existingTruthGroupCount)) truthStore.applyLearnedGuesses(session) else 0"
        "                runOnUiThread {"
-       "                    if (!isCurrentLoad(expectedGeneration, uri, expectedPageIndex)) return@runOnUiThread"
+       "                    if (isStaleLoad(expectedGeneration, uri, expectedPageIndex)) return@runOnUiThread"
        "                    currentSession = session"
        "                    selectedGroupId = null"
 	       "                    reviewView.session = session"
@@ -6598,7 +6613,7 @@
        "    private fun mergeRemoteTruthAsync(session: SsdSession, generation: Int, uri: Uri, page: Int) {"
        "        thread {"
        "            val matches = truthStore.pullRemoteTruth(session)"
-       "            if (!positiveCount(matches)) return@thread"
+       "            if (nonPositiveCount(matches)) return@thread"
        "            val truth = truthStore.loadTruth(session) ?: return@thread"
        "            val merged = session.copy("
        "                cells = mutableListOf(),"
@@ -6610,7 +6625,7 @@
        "            truthStore.applyTruth(merged)"
        "            ssdSessionPruneGroupsOutsideSsdArea(merged)"
        "            runOnUiThread {"
-       "                if (!isCurrentLoad(generation, uri, page)) return@runOnUiThread"
+       "                if (isStaleLoad(generation, uri, page)) return@runOnUiThread"
        "                currentSession = merged"
        "                selectedGroupId = null"
        "                reviewView.session = merged"
@@ -6626,6 +6641,9 @@
 	       "    private fun isCurrentLoad(generation: Int, uri: Uri, page: Int): Boolean ="
 	       "        currentLoadMatches(generation, loadGeneration, pdfUri, uri, pageIndex, page)"
 	       ""
+	       "    private fun isStaleLoad(generation: Int, uri: Uri, page: Int): Boolean ="
+	       "        currentLoadStale(generation, loadGeneration, pdfUri, uri, pageIndex, page)"
+	       ""
 	       "    private fun handleAreaSelected(rect: FloatArray, detectedIds: List<String>) {"
 	       "        if (currentSession == null) {"
 	       "            setStatus(selectionIgnoredLoadingStatus())"