Generate SSD export file traversals from typed Jerboa

ober

d260265a96db011c36480a16cdcdaf93ea8fba51

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 4e144fe..b34ee77 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -2036,6 +2036,7 @@
                       (java security cert CertificateException)
                       (java security cert X509Certificate)
                       (java util zip ZipEntry)
+                      (kotlin io FileTreeWalk)
                       (javax net ssl X509TrustManager))
       (typed-library (com sfb ssdreview)
         (export make-RemoteConfig RemoteConfig?
@@ -2089,9 +2090,10 @@
                 truthStoreMakePinnedTrustManager
                 zipEntryPresent
                 zipEntryIsDirectory zipEntryIsFile zipEntryDeclaresExcessiveSize
-                forEachFileArray forEachFileUntil
+                forEachFileArray forEachFileUntil forEachRegularFileUnder
                 countInstalledStagedZipEntries)
         (type File)
+        (type FileTreeWalk)
         (type Path)
         (type CertificateException)
         (type MessageDigest)
@@ -2116,6 +2118,11 @@
           (kotlin-member-call listFiles))
         (extern (fileParentFileRaw (file : File)) : (Nullable File)
           (kotlin-member-get parentFile))
+        (extern (truthFileWalkTopDownRaw (file : File)) : FileTreeWalk
+          (kotlin-member-call walkTopDown))
+        (extern (truthFileTreeWalkToListRaw
+                  (walk : FileTreeWalk)) : (List File)
+          (kotlin-member-call toList))
         (def (forEachFileArray
                (files : (Array File))
                (action : (-> File Unit))) : Unit
@@ -2126,6 +2133,22 @@
                 (invoke action (list-ref files i))
                 ignored))
             (begin)))
+        (def (forEachRegularFileUnder
+               (dir : File)
+               (action : (-> File Unit))) : Unit
+          (let ((files
+                  (truthFileTreeWalkToListRaw
+                    (truthFileWalkTopDownRaw dir))))
+            (begin
+              (for/fold ((ignored (int32 0)))
+                        ((i (in-range (int32 0) (list-size files))))
+                (let ((file (list-ref files i)))
+                  (begin
+                    (if (fileIsRegular file)
+                      (invoke action file)
+                      (begin))
+                    ignored)))
+              (begin))))
         (def (forEachFileUntil
                (files : (List File))
                (action : (-> File Bool))) : Unit
@@ -12092,8 +12115,8 @@
        "        var entries = 0"
        "        var sourceBytes = 0L"
        "        ZipOutputStream(BufferedOutputStream(limited)).use { zip ->"
-       "            listOf(groundTruthDir, learnedDir, eventsDir, reviewsDir).forEach { dir ->"
-       "                dir.walkTopDown().filter { fileIsRegular(it) }.forEach { file ->"
+       "            forEachFileUntil(listOf(groundTruthDir, learnedDir, eventsDir, reviewsDir)) { dir ->"
+       "                forEachRegularFileUnder(dir) { file ->"
        "                    if (fileIsSymbolicLink(file)) throw IllegalStateException(\"Storage symlink rejected\")"
        "                    entries += 1"
        "                    sourceBytes += fileSizeBytes(file)"
@@ -12108,6 +12131,7 @@
        "                    }"
        "                    zip.closeEntry()"
        "                }"
+       "                true"
        "            }"
        "        }"
        "    }"
@@ -12317,9 +12341,9 @@
        "        storageStats()"
        "        var count = 0"
        "        var bytes = 0L"
-       "        listOf(groundTruthDir, learnedDir, eventsDir, reviewsDir).forEach { dir ->"
+       "        forEachFileUntil(listOf(groundTruthDir, learnedDir, eventsDir, reviewsDir)) { dir ->"
        "            val targetDir = ensureTreeDir(tree, dir.name)"
-       "            dir.walkTopDown().filter { fileIsRegular(it) }.forEach { file ->"
+       "            forEachRegularFileUnder(dir) { file ->"
        "                count += 1"
        "                bytes += fileSizeBytes(file)"
        "                if (treeExportQuotaExceeded(count, bytes, MAX_ZIP_ENTRIES, MAX_ZIP_TOTAL_BYTES)) {"
@@ -12328,6 +12352,7 @@
        "                val relative = dir.toPath().relativize(file.toPath()).toString()"
        "                writeTreeFile(targetDir, relative, file)"
        "            }"
+       "            true"
        "        }"
        "        return count"
        "    }"