Move SSD truth index extraction to typed Kotlin

ober

240d6a4350a4ae494a8406a76d6ab4bf0a37b6a5

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 677699a..0e7f32f 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -1593,7 +1593,8 @@
     (typed-kotlin-file "com/sfb/ssdreview/TruthJson.kt"
       (kotlin-imports (org json JSONArray) (org json JSONObject))
       (typed-library (com sfb ssdreview)
-        (export truthGroupCount imageWidth imageHeight)
+        (export truthJsonArrayOrEmpty truthJsonObjectOrEmpty
+                truthGroupCount imageWidth imageHeight)
         (type JSONArray)
         (type JSONObject)
         (type Int32)
@@ -3510,12 +3511,14 @@
               out)))))
 
     (typed-kotlin-file "com/sfb/ssdreview/TruthStoreJson.kt"
-      (kotlin-imports (org json JSONArray) (org json JSONObject))
+      (kotlin-imports (java io File) (org json JSONArray) (org json JSONObject))
       (typed-library (com sfb ssdreview)
-        (export learnedExamplesDocument autosaveTruthPayloadJson)
+        (export learnedExamplesDocument autosaveTruthPayloadJson truthIndexEntryFromJson)
+        (type File)
         (type JSONArray)
         (type JSONObject)
         (type Int32)
+        (type TruthIndexEntry)
         (def (learnedExamplesDocument (examples : JSONArray) (maxExamples : Int32)) : JSONObject
           (let ((out (json-object-empty)))
             (begin
@@ -3530,7 +3533,27 @@
             (begin
               (json-object-put-string! out "reason" "android_autosave")
               (json-object-put-json-object! out "truth" truth)
-              out)))))
+              out)))
+        (def (truthIndexEntryFromJson (file : File)
+                                      (truth : JSONObject)
+                                      (resolvedTruthTime : Int)) : (Nullable TruthIndexEntry)
+          (let ((source (truthJsonObjectOrEmpty (json-object-opt-json-object truth "source"))))
+            (let ((sourceName
+                    (sourceBaseName
+                      (json-object-opt-string-default
+                        source
+                        "name"
+                        (json-object-opt-string source "path")))))
+              (nullable-some
+                (make-TruthIndexEntry
+                  file
+                  (json-object-opt-string truth "source_key")
+                  sourceName
+                  (canonicalSourceName sourceName)
+                  (json-object-opt-int32-default source "page" (int32 -1))
+                  (json-object-opt-int32-default source "dpi" (int32 -1))
+                  (truthGroupCount truth)
+                  resolvedTruthTime)))))))
 
     (typed-kotlin-file "com/sfb/ssdreview/ClientLogJson.kt"
       (kotlin-imports (org json JSONObject))
@@ -6522,18 +6545,7 @@
        "        if (!file.exists() || !file.isFile) return null"
        "        return try {"
        "            val truth = JSONObject(readLocalText(file))"
-       "            val source = truth.optJSONObject(\"source\") ?: JSONObject()"
-       "            val sourceName = sourceBaseName(source.optString(\"name\", source.optString(\"path\")))"
-       "            TruthIndexEntry("
-       "                _file = file,"
-       "                sourceKey = truth.optString(\"source_key\"),"
-       "                sourceName = sourceName,"
-       "                canonicalSourceName = canonicalSourceName(sourceName),"
-       "                page = source.optInt(\"page\", -1),"
-       "                dpi = source.optInt(\"dpi\", -1),"
-       "                groupCount = truthGroupCount(truth),"
-       "                truthTime = truthTime(truth) ?: file.lastModified()"
-       "            )"
+       "            truthIndexEntryFromJson(file, truth, truthTime(truth) ?: file.lastModified())"
        "        } catch (_: Exception) {"
        "            null"
        "        }"