Move SSD storage URL branches to typed Kotlin

ober

0d81a2726a33c0b7288219c1aa45f49daaa8b630

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index ab1f686..79d7133 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -1534,10 +1534,19 @@
                 TruthIndexEntry-file TruthIndexEntry-sourceKey
                 TruthIndexEntry-sourceName TruthIndexEntry-canonicalSourceName
                 TruthIndexEntry-page TruthIndexEntry-dpi TruthIndexEntry-groupCount
-                TruthIndexEntry-truthTime betterTruthIndex)
+                TruthIndexEntry-truthTime betterTruthIndex
+                oldDestinationBytes urlEffectivePort)
         (type File)
         (type URL)
         (type Int32)
+        (extern (fileIsFile (file : File)) : Bool
+          (kotlin-member-get isFile))
+        (extern (fileLength (file : File)) : Int
+          (kotlin-member-call length))
+        (extern (urlPort (url : URL)) : Int32
+          (kotlin-member-get port))
+        (extern (urlDefaultPort (url : URL)) : Int32
+          (kotlin-member-get defaultPort))
         (record RemoteConfig
           ((apiUrl : URL)
            (spkiSha256 : (Nullable Bytes))
@@ -1562,7 +1571,12 @@
               (and (equal? (TruthIndexEntry-groupCount candidate)
                            (TruthIndexEntry-groupCount current))
                    (> (TruthIndexEntry-truthTime candidate)
-                      (TruthIndexEntry-truthTime current)))))))
+                      (TruthIndexEntry-truthTime current)))))
+        (def (oldDestinationBytes (destination : File)) : Int
+          (if (fileIsFile destination) (fileLength destination) (int 0)))
+        (def (urlEffectivePort (url : URL)) : Int32
+          (let ((port (urlPort url)))
+            (if (>= port (int32 0)) port (urlDefaultPort url))))))
 
     (typed-kotlin-file "com/sfb/ssdreview/JsonGeometry.kt"
       (kotlin-imports (org json JSONArray))
@@ -7775,7 +7789,7 @@
        "    private fun ensureStorageCapacity(destination: File, bytes: Long) {"
        "        require(bytes in 0..MAX_ZIP_ENTRY_BYTES) { \"Per-file storage limit exceeded\" }"
        "        val stats = storageStats()"
-       "        val oldBytes = if (destination.isFile) destination.length() else 0L"
+       "        val oldBytes = oldDestinationBytes(destination)"
        "        val newFiles = stats.files + if (destination.exists()) 0 else 1"
        "        val newBytes = stats.bytes - oldBytes + bytes"
        "        require(newFiles <= MAX_STORAGE_FILES && newBytes <= MAX_STORAGE_BYTES) {"
@@ -7839,8 +7853,8 @@
        "        require(relative.isNotBlank() && !relative.startsWith('/') && !relative.contains(\"..\"))"
        "        val config = remoteConfig ?: throw IllegalStateException(\"Remote SSD service is not locally configured\")"
        "        val candidate = URL(config.apiUrl.toString().trimEnd('/') + \"/\" + relative)"
-       "        val basePort = if (config.apiUrl.port >= 0) config.apiUrl.port else config.apiUrl.defaultPort"
-       "        val candidatePort = if (candidate.port >= 0) candidate.port else candidate.defaultPort"
+       "        val basePort = urlEffectivePort(config.apiUrl)"
+       "        val candidatePort = urlEffectivePort(candidate)"
        "        require(candidate.protocol == config.apiUrl.protocol && candidate.host == config.apiUrl.host && candidatePort == basePort) {"
        "            \"Remote URL escaped configured HTTPS origin\""
        "        }"