Refactor WebsocketHandler into a standalone class file
This commit is contained in:
@@ -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) {
|
||||||
|
|||||||
164
src/jvmMain/kotlin/nl/astraeus/vst/base/web/WebsocketHandler.kt
Normal file
164
src/jvmMain/kotlin/nl/astraeus/vst/base/web/WebsocketHandler.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user