Generate SSD MediaStore cursor scan from typed Jerboa

ober

5bc8b2ed63633ccad8283272337d779c6b77a8af

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 397c32e..6b3f36e 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -4747,6 +4747,7 @@
                       (java util UUID)
                       (android app AlertDialog)
                       (android app AlertDialog Builder)
+                      (android content ContentUris)
                       (android content ContentResolver)
                       (android content Context)
                       (android content DialogInterface)
@@ -4852,6 +4853,7 @@
                 reviewViewClearInteractionState
                 reviewViewMakeScaleGestureDetector
                 forEachDocumentFile
+                collectSsdPdfChoicesFromMediaCursor
                 mainActivityInstallReviewViewCallbacks
                 mainActivitySelectGroupId
                 mainActivitySelectRequestedGroup
@@ -5030,6 +5032,19 @@
           (kotlin-member-get isFile))
         (extern (documentFileDelete (file : DocumentFile)) : Bool
           (kotlin-member-call delete))
+        (extern (cursorMoveToNextRaw (cursor : Cursor)) : Bool
+          (kotlin-member-call moveToNext))
+        (extern (cursorCountRaw (cursor : Cursor)) : Int32
+          (kotlin-member-get count))
+        (extern (cursorGetStringRaw (cursor : Cursor)
+                                    (column : Int32)) : (Nullable String)
+          (kotlin-member-call getString))
+        (extern (cursorGetLongRaw (cursor : Cursor)
+                                  (column : Int32)) : Int
+          (kotlin-member-call getLong))
+        (extern (contentUrisWithAppendedIdRaw (uri : Uri)
+                                             (id : Int)) : Uri
+          (kotlin-call ContentUris withAppendedId))
         (extern (contentResolverOpenInputStreamRaw
                   (resolver : ContentResolver)
                   (uri : Uri)) : (Nullable InputStream)
@@ -5826,6 +5841,53 @@
                 (invoke action (list-ref files i))
                 ignored))
             (begin)))
+        (def (trimSlashes (value : String)) : String
+          (string-replace-regex
+            (string-replace-regex value "^/+" "")
+            "/+$"
+            ""))
+        (def (appendSsdPdfChoiceFromMediaCursor
+               (cursor : Cursor)
+               (choices : (MutableList SsdPdfChoice))
+               (idCol : Int32)
+               (nameCol : Int32)
+               (pathCol : Int32)
+               (baseUri : Uri)
+               (limit : Int32)) : Unit
+          (let ((rawName (cursorGetStringRaw cursor nameCol)))
+            (if (nullableTextPresent rawName)
+              (let ((name (nullableTextOrEmpty rawName)))
+                (if (shouldSkipSsdPdfChoiceName name (list-size choices) limit)
+                  (begin)
+                  (let ((id (cursorGetLongRaw cursor idCol))
+                        (relativePath
+                          (trimSlashes
+                            (nullableTextOrEmpty
+                              (cursorGetStringRaw cursor pathCol)))))
+                    (mutable-list-add!
+                      choices
+                      (make-SsdPdfChoice
+                        (ssdPdfDownloadLabel relativePath name)
+                        (contentUrisWithAppendedIdRaw baseUri id))))))
+              (begin))))
+        (def (collectSsdPdfChoicesFromMediaCursor
+               (cursor : Cursor)
+               (choices : (MutableList SsdPdfChoice))
+               (idCol : Int32)
+               (nameCol : Int32)
+               (pathCol : Int32)
+               (baseUri : Uri)
+               (limit : Int32)) : Unit
+          (begin
+            (for/fold ((ignored (int32 0)))
+                      ((i (in-range (int32 0) (cursorCountRaw cursor))))
+              (begin
+                (if (cursorMoveToNextRaw cursor)
+                  (appendSsdPdfChoiceFromMediaCursor
+                    cursor choices idCol nameCol pathCol baseUri limit)
+                  (begin))
+                ignored))
+            (begin)))
         (def (trimmedNonBlankLines (text : String)) : (MutableList String)
           (let ((lines (stringLines text)))
             (for/fold ((out (mutable-list-empty String)))
@@ -10324,16 +10386,15 @@
        "            val idCol = cursor.getColumnIndexOrThrow(MediaStore.Downloads._ID)"
        "            val nameCol = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME)"
        "            val pathCol = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.RELATIVE_PATH)"
-       "            while (cursor.moveToNext()) {"
-       "                val rawName = cursor.getString(nameCol)"
-       "                if (!nullableTextPresent(rawName)) continue"
-       "                val name = nullableTextOrEmpty(rawName)"
-       "                if (shouldSkipSsdPdfChoiceName(name, choices.size, MAX_SSD_PDF_CHOICES)) continue"
-       "                val id = cursor.getLong(idCol)"
-       "                val relativePath = nullableTextOrEmpty(cursor.getString(pathCol)).trim('/')"
-       "                val label = ssdPdfDownloadLabel(relativePath, name)"
-       "                choices.add(SsdPdfChoice(label, ContentUris.withAppendedId(MediaStore.Downloads.EXTERNAL_CONTENT_URI, id)))"
-       "            }"
+       "            collectSsdPdfChoicesFromMediaCursor("
+       "                cursor,"
+       "                choices,"
+       "                idCol,"
+       "                nameCol,"
+       "                pathCol,"
+       "                MediaStore.Downloads.EXTERNAL_CONTENT_URI,"
+       "                MAX_SSD_PDF_CHOICES"
+       "            )"
        "        }"
        "        return choices"
        "    }"