Use typed file size checks in SSD storage

ober

f1783d4cec691eba7ae717d31d0fbe38cba0ee6a

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 5c50b9b..13ac92d 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -1574,6 +1574,7 @@
                 TruthIndexEntry-page TruthIndexEntry-dpi TruthIndexEntry-groupCount
                 TruthIndexEntry-truthTime betterTruthIndex
                 fileExists fileIsDirectory fileIsRegular fileIsSymbolicLink
+                fileSizeBytes
                 pathIsSymbolicLink safeLocalRegularFile
                 destinationNewFileDelta
                 oldDestinationBytes urlEffectivePort
@@ -1644,6 +1645,8 @@
           (and (fileExists file) (fileIsDirectoryRaw file)))
         (def (fileIsRegular (file : File)) : Bool
           (and (fileExists file) (fileIsFile file)))
+        (def (fileSizeBytes (file : File)) : Int
+          (fileLength file))
         (def (pathIsSymbolicLink (path : Path)) : Bool
           (pathIsSymbolicLinkRaw path))
         (def (fileIsSymbolicLink (file : File)) : Bool
@@ -8087,7 +8090,7 @@
        "                file.listFiles()?.forEach { walk(it, depth + 1) }"
        "            } else if (fileIsRegular(file)) {"
        "                stats.files += 1"
-       "                stats.bytes += file.length()"
+       "                stats.bytes += fileSizeBytes(file)"
        "                if (stats.files > MAX_STORAGE_FILES || stats.bytes > MAX_STORAGE_BYTES) {"
        "                    throw IllegalStateException(\"Storage quota exceeded\")"
        "                }"
@@ -8330,8 +8333,8 @@
        "                dir.walkTopDown().filter { fileIsRegular(it) }.forEach { file ->"
        "                    if (fileIsSymbolicLink(file)) throw IllegalStateException(\"Storage symlink rejected\")"
        "                    entries += 1"
-       "                    sourceBytes += file.length()"
-       "                    if (entries > MAX_ZIP_ENTRIES || file.length() > MAX_ZIP_ENTRY_BYTES || sourceBytes > MAX_ZIP_TOTAL_BYTES) {"
+       "                    sourceBytes += fileSizeBytes(file)"
+       "                    if (entries > MAX_ZIP_ENTRIES || fileSizeBytes(file) > MAX_ZIP_ENTRY_BYTES || sourceBytes > MAX_ZIP_TOTAL_BYTES) {"
        "                        throw IllegalStateException(\"ZIP export quota exceeded\")"
        "                    }"
        "                    val relative = root.toPath().relativize(file.toPath()).toString()"
@@ -8557,7 +8560,7 @@
        "            val targetDir = ensureTreeDir(tree, dir.name)"
        "            dir.walkTopDown().filter { fileIsRegular(it) }.forEach { file ->"
        "                count += 1"
-       "                bytes += file.length()"
+       "                bytes += fileSizeBytes(file)"
        "                if (count > MAX_ZIP_ENTRIES || bytes > MAX_ZIP_TOTAL_BYTES) {"
        "                    throw IllegalStateException(\"Tree export quota exceeded\")"
        "                }"
@@ -8628,7 +8631,7 @@
        ""
        "    private fun writeTreeFile(parent: DocumentFile, relativeName: String, source: File) {"
        "        require(safeLocalRegularFile(source))"
-       "        require(source.length() <= MAX_ZIP_ENTRY_BYTES) { \"Tree export file exceeds byte limit\" }"
+       "        require(fileSizeBytes(source) <= MAX_ZIP_ENTRY_BYTES) { \"Tree export file exceeds byte limit\" }"
        "        val parts = trimmedNonBlankPathParts(relativeName, File.separatorChar, '/')"
        "        require(safeTreePathParts(parts, MAX_ZIP_DEPTH))"
        "        val fileName = treePathLeaf(parts)"