Move SSD output stream guards to typed Kotlin

ober

2fcf675d25576a1190a5d05ea9d33973cf085d95

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 7d57e53..6d559e2 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -6045,6 +6045,7 @@
 
     (typed-kotlin-file "com/sfb/ssdreview/NetworkSafety.kt"
       (kotlin-imports (java io InputStream)
+                      (java io OutputStream)
                       (java io BufferedInputStream)
                       (java net URLConnection)
                       (java util zip GZIPInputStream))
@@ -6052,8 +6053,9 @@
         (export normalizeRemoteEndpointValue normalizeRemoteEndpoint urlEncode
                 contentEncodingIsGzip responseInputStream
                 connectionContentLengthExceedsLimit
-                inputStreamPresent localTruthHttpAllowed)
+                inputStreamPresent outputStreamPresent localTruthHttpAllowed)
         (type InputStream)
+        (type OutputStream)
         (type URLConnection)
         (extern (bufferedInputStream (input : InputStream)) : InputStream
           (kotlin-call java io BufferedInputStream))
@@ -6090,6 +6092,8 @@
               raw))
         (def (inputStreamPresent (input : (Nullable InputStream))) : Bool
           (not (nullable-null? input)))
+        (def (outputStreamPresent (output : (Nullable OutputStream))) : Bool
+          (not (nullable-null? output)))
         (def (connectionContentLengthExceedsLimit (connection : URLConnection)
                                                   (limit : Int)) : Bool
           (> (connectionContentLength connection) limit))
@@ -7302,14 +7306,20 @@
        "        } else if (requestIsOpenSsdPdfTree(requestCode, REQ_OPEN_SSD_PDF_TREE)) {"
        "            rememberAndBrowseSsdFolder(uri, flags)"
        "        } else if (requestIsExportZip(requestCode, REQ_EXPORT_ZIP)) {"
-       "            contentResolver.openOutputStream(uri)?.use {"
-       "                truthStore.exportZip(it)"
-       "                setStatus(exportedTruthZipStatus())"
+       "            val output = contentResolver.openOutputStream(uri)"
+       "            if (outputStreamPresent(output)) {"
+       "                checkNotNull(output).use {"
+       "                    truthStore.exportZip(it)"
+       "                    setStatus(exportedTruthZipStatus())"
+       "                }"
        "            }"
        "        } else if (requestIsImportZip(requestCode, REQ_IMPORT_ZIP)) {"
-       "            contentResolver.openInputStream(uri)?.use {"
-       "                val count = truthStore.importZip(it)"
-       "                setStatus(importedTruthZipStatus(count, truthStore.storageSummary()))"
+       "            val input = contentResolver.openInputStream(uri)"
+       "            if (inputStreamPresent(input)) {"
+       "                checkNotNull(input).use {"
+       "                    val count = truthStore.importZip(it)"
+       "                    setStatus(importedTruthZipStatus(count, truthStore.storageSummary()))"
+       "                }"
        "            }"
        "        } else if (requestIsSyncTree(requestCode, REQ_SYNC_TREE)) {"
        "            syncSelectedTree(uri, flags)"
@@ -9331,7 +9341,9 @@
        "        val mime = treeFileMime(fileName)"
        "        val doc = dir.createFile(mime, fileName)"
        "            ?: throw IllegalStateException(unableToCreateFileMessage(fileName))"
-       "        context.contentResolver.openOutputStream(doc.uri)?.use { output ->"
+       "        val outputStream = context.contentResolver.openOutputStream(doc.uri)"
+       "        if (!outputStreamPresent(outputStream)) return"
+       "        checkNotNull(outputStream).use { output ->"
        "            source.inputStream().use { input ->"
        "                LimitedInputStream(input, MAX_ZIP_ENTRY_BYTES).use { it.copyTo(output, 32 * 1024) }"
        "            }"