Refactor WebsocketHandler into a standalone class file

This commit is contained in:
2025-06-10 20:08:28 +02:00
parent 0bdaa5c94f
commit 8ee8f17f96
2 changed files with 164 additions and 160 deletions

View File

@@ -10,21 +10,7 @@ import io.undertow.server.session.Session
import io.undertow.server.session.SessionCookieConfig import io.undertow.server.session.SessionCookieConfig
import io.undertow.server.session.SessionManager import io.undertow.server.session.SessionManager
import io.undertow.util.Headers 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.nio.file.Paths
import java.security.MessageDigest
object VstSessionConfig { object VstSessionConfig {
val config = SessionCookieConfig() 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 { object WebsocketConnectHandler : HttpHandler {
override fun handleRequest(exchange: HttpServerExchange) { override fun handleRequest(exchange: HttpServerExchange) {

View File

@@ -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)
}
}
}
}