From 8ee8f17f96992b6f45595d9abdaf19e1edbd4f28 Mon Sep 17 00:00:00 2001 From: rnentjes Date: Tue, 10 Jun 2025 20:08:28 +0200 Subject: [PATCH] Refactor `WebsocketHandler` into a standalone class file --- .../astraeus/vst/base/web/RequestHandler.kt | 160 ----------------- .../astraeus/vst/base/web/WebsocketHandler.kt | 164 ++++++++++++++++++ 2 files changed, 164 insertions(+), 160 deletions(-) create mode 100644 src/jvmMain/kotlin/nl/astraeus/vst/base/web/WebsocketHandler.kt diff --git a/src/jvmMain/kotlin/nl/astraeus/vst/base/web/RequestHandler.kt b/src/jvmMain/kotlin/nl/astraeus/vst/base/web/RequestHandler.kt index e2ec1d7..d92b4fc 100644 --- a/src/jvmMain/kotlin/nl/astraeus/vst/base/web/RequestHandler.kt +++ b/src/jvmMain/kotlin/nl/astraeus/vst/base/web/RequestHandler.kt @@ -10,21 +10,7 @@ import io.undertow.server.session.Session import io.undertow.server.session.SessionCookieConfig import io.undertow.server.session.SessionManager import io.undertow.util.Headers -import io.undertow.websockets.WebSocketConnectionCallback -import io.undertow.websockets.core.AbstractReceiveListener -import io.undertow.websockets.core.BufferedBinaryMessage -import io.undertow.websockets.core.BufferedTextMessage -import io.undertow.websockets.core.WebSocketChannel -import io.undertow.websockets.core.WebSockets -import io.undertow.websockets.spi.WebSocketHttpExchange -import nl.astraeus.vst.base.Settings -import nl.astraeus.vst.base.db.Database -import nl.astraeus.vst.base.db.PatchDao -import nl.astraeus.vst.base.db.PatchEntity -import java.io.File -import java.nio.ByteBuffer import java.nio.file.Paths -import java.security.MessageDigest object VstSessionConfig { val config = SessionCookieConfig() @@ -34,152 +20,6 @@ object VstSessionConfig { } } -class WebsocketHandler( - val session: Session? -) : AbstractReceiveListener(), WebSocketConnectionCallback { - - companion object { - // Ensure the data directory exists - private val dataDir = File(Settings.dataDir).apply { - if (!exists()) { - mkdirs() - } - } - - fun fileExists(hash: String): Boolean { - check(hash.length == 64) { "Hash must be 64 characters long" } - - var currentDir = dataDir - var remaining = hash - while(remaining.length > 8) { - val subDir = remaining.substring(0, 8) - currentDir = File(currentDir, subDir) - if (!currentDir.exists()) { - return false - } - remaining = remaining.substring(8) - } - - return File(currentDir, remaining).exists() - } - - // Get file from hash, using subdirectories based on hash - fun getFileFromHash(hash: String): File { - check(hash.length == 64) { "Hash must be 64 characters long" } - - var currentDir = dataDir - var remaining = hash - while(remaining.length > 8) { - val subDir = remaining.substring(0, 8) - currentDir = File(currentDir, subDir) - if (!currentDir.exists()) { - currentDir.mkdirs() - } - remaining = remaining.substring(8) - } - - return File(currentDir, remaining) - } - - // Create SHA-1 hash from binary data - fun createHashFromBytes(bytes: ByteArray): String { - val md = MessageDigest.getInstance("SHA-256") - val digest = md.digest(bytes) - return digest.joinToString("") { "%02x".format(it) } - } - } - - override fun onConnect(exchange: WebSocketHttpExchange, channel: WebSocketChannel) { - channel.receiveSetter.set(this) - channel.resumeReceives() - } - - override fun onFullTextMessage(channel: WebSocketChannel, message: BufferedTextMessage) { - val vstSession = session?.getAttribute("html-session") as? VstSession - - val data = message.data - val commandLength = data.indexOf('\n') - if (commandLength > 0) { - val command = data.substring(0, commandLength) - val value = data.substring(commandLength + 1) - - when (command) { - "SAVE" -> { - val patchId = vstSession?.patchId - if (patchId != null) { - Database.transaction { - val patchEntity = PatchDao.findById(patchId) - - if (patchEntity != null) { - PatchDao.update(patchEntity.copy(patch = value)) - } else { - PatchDao.insert(PatchEntity(0, patchId, value)) - } - } - WebSockets.sendText("SAVED\n$patchId", channel, null) - } - } - - "LOAD" -> { - val patchId = vstSession?.patchId - if (patchId != null) { - Database.transaction { - val patchEntity = PatchDao.findById(patchId) - - if (patchEntity != null) { - WebSockets.sendText("LOAD\n${patchEntity.patch}", channel, null) - } - } - } - } - - "LOAD_BINARY" -> { - val hash = value.trim() - if (hash.isNotEmpty()) { - if (fileExists(hash)) { - val file = getFileFromHash(hash) - if (file.exists() && file.isFile) { - val bytes = file.readBytes() - WebSockets.sendBinary(ByteBuffer.wrap(bytes), channel, null) - } - } - } - } - } - } - } - - override fun onFullBinaryMessage(channel: WebSocketChannel?, message: BufferedBinaryMessage?) { - // Process binary message: create hash from binary, save file in data/files/ directory, - // sub directories are 5 characters of the hash per directory - if (channel != null && message != null) { - try { - // Get the binary data - val pooled = message.data - val buffer = pooled.resource[0] // Get the first ByteBuffer - - // Convert ByteBuffer to ByteArray - val bytes = ByteArray(buffer.remaining()) - buffer.get(bytes) - - // Free the pooled resource - pooled.free() - - // Create hash from binary data - val hash = createHashFromBytes(bytes) - - // Save file in data/files/ directory with subdirectories - val file = getFileFromHash(hash) - file.writeBytes(bytes) - - // Send the hash back to the client - WebSockets.sendText("BINARY_SAVED\n$hash", channel, null) - } catch (e: Exception) { - WebSockets.sendText("ERROR\n${e.message}", channel, null) - } - } - } -} object WebsocketConnectHandler : HttpHandler { override fun handleRequest(exchange: HttpServerExchange) { diff --git a/src/jvmMain/kotlin/nl/astraeus/vst/base/web/WebsocketHandler.kt b/src/jvmMain/kotlin/nl/astraeus/vst/base/web/WebsocketHandler.kt new file mode 100644 index 0000000..98af233 --- /dev/null +++ b/src/jvmMain/kotlin/nl/astraeus/vst/base/web/WebsocketHandler.kt @@ -0,0 +1,164 @@ +package nl.astraeus.vst.base.web + +import io.undertow.server.session.Session +import io.undertow.websockets.WebSocketConnectionCallback +import io.undertow.websockets.core.AbstractReceiveListener +import io.undertow.websockets.core.BufferedBinaryMessage +import io.undertow.websockets.core.BufferedTextMessage +import io.undertow.websockets.core.WebSocketChannel +import io.undertow.websockets.core.WebSockets +import io.undertow.websockets.spi.WebSocketHttpExchange +import nl.astraeus.vst.base.Settings +import nl.astraeus.vst.base.db.Database +import nl.astraeus.vst.base.db.PatchDao +import nl.astraeus.vst.base.db.PatchEntity +import java.io.File +import java.nio.ByteBuffer +import java.security.MessageDigest + +class WebsocketHandler( + val session: Session? +) : AbstractReceiveListener(), WebSocketConnectionCallback { + + companion object { + // Ensure the data directory exists + private val dataDir = File(Settings.dataDir).apply { + if (!exists()) { + mkdirs() + } + } + + fun fileExists(hash: String): Boolean { + check(hash.length == 64) { "Hash must be 64 characters long" } + + var currentDir = dataDir + var remaining = hash + while(remaining.length > 8) { + val subDir = remaining.substring(0, 8) + currentDir = File(currentDir, subDir) + if (!currentDir.exists()) { + return false + } + remaining = remaining.substring(8) + } + + return File(currentDir, remaining).exists() + } + + // Get file from hash, using subdirectories based on hash + fun getFileFromHash(hash: String): File { + check(hash.length == 64) { "Hash must be 64 characters long" } + + var currentDir = dataDir + var remaining = hash + while(remaining.length > 8) { + val subDir = remaining.substring(0, 8) + currentDir = File(currentDir, subDir) + if (!currentDir.exists()) { + currentDir.mkdirs() + } + remaining = remaining.substring(8) + } + + return File(currentDir, remaining) + } + + // Create SHA-1 hash from binary data + fun createHashFromBytes(bytes: ByteArray): String { + val md = MessageDigest.getInstance("SHA-256") + val digest = md.digest(bytes) + return digest.joinToString("") { "%02x".format(it) } + } + } + + override fun onConnect(exchange: WebSocketHttpExchange, channel: WebSocketChannel) { + channel.receiveSetter.set(this) + channel.resumeReceives() + } + + override fun onFullTextMessage(channel: WebSocketChannel, message: BufferedTextMessage) { + val vstSession = session?.getAttribute("html-session") as? VstSession + + val data = message.data + val commandLength = data.indexOf('\n') + if (commandLength > 0) { + val command = data.substring(0, commandLength) + val value = data.substring(commandLength + 1) + + when (command) { + "SAVE" -> { + val patchId = vstSession?.patchId + if (patchId != null) { + Database.transaction { + val patchEntity = PatchDao.findById(patchId) + + if (patchEntity != null) { + PatchDao.update(patchEntity.copy(patch = value)) + } else { + PatchDao.insert(PatchEntity(0, patchId, value)) + } + } + WebSockets.sendText("SAVED\n$patchId", channel, null) + } + } + + "LOAD" -> { + val patchId = vstSession?.patchId + if (patchId != null) { + Database.transaction { + val patchEntity = PatchDao.findById(patchId) + + if (patchEntity != null) { + WebSockets.sendText("LOAD\n${patchEntity.patch}", channel, null) + } + } + } + } + + "LOAD_BINARY" -> { + val hash = value.trim() + if (hash.isNotEmpty()) { + if (fileExists(hash)) { + val file = getFileFromHash(hash) + if (file.exists() && file.isFile) { + val bytes = file.readBytes() + WebSockets.sendBinary(ByteBuffer.wrap(bytes), channel, null) + } + } + } + } + } + } + } + + override fun onFullBinaryMessage(channel: WebSocketChannel?, message: BufferedBinaryMessage?) { + // Process binary message: create hash from binary, save file in data/files/ directory, + // sub directories are 5 characters of the hash per directory + if (channel != null && message != null) { + try { + // Get the binary data + val pooled = message.data + val buffer = pooled.resource[0] // Get the first ByteBuffer + + // Convert ByteBuffer to ByteArray + val bytes = ByteArray(buffer.remaining()) + buffer.get(bytes) + + // Free the pooled resource + pooled.free() + + // Create hash from binary data + val hash = createHashFromBytes(bytes) + + // Save file in data/files/ directory with subdirectories + val file = getFileFromHash(hash) + file.writeBytes(bytes) + + // Send the hash back to the client + WebSockets.sendText("BINARY_SAVED\n$hash", channel, null) + } catch (e: Exception) { + WebSockets.sendText("ERROR\n${e.message}", channel, null) + } + } + } +}