diff --git a/src/jvmMain/kotlin/nl/astraeus/vst/base/Settings.kt b/src/jvmMain/kotlin/nl/astraeus/vst/base/Settings.kt index ecf1d83..92ef26c 100644 --- a/src/jvmMain/kotlin/nl/astraeus/vst/base/Settings.kt +++ b/src/jvmMain/kotlin/nl/astraeus/vst/base/Settings.kt @@ -8,10 +8,11 @@ object Settings { var port = 9004 var connectionTimeout = 30000 var jdbcStatsPort = 6001 + var dataDir = "data" var jdbcDriver = "nl.astraeus.jdbc.Driver" val jdbcConnectionUrl - get() = "jdbc:stat:webServerPort=$jdbcStatsPort:jdbc:sqlite:data/vst.db" + get() = "jdbc:stat:webServerPort=$jdbcStatsPort:jdbc:sqlite:$dataDir/vst.db" var jdbcUser = "sa" var jdbcPassword = "" @@ -35,6 +36,8 @@ object Settings { port = properties.getProperty("port", port.toString()).toInt() jdbcStatsPort = properties.getProperty("jdbcStatsPort", jdbcStatsPort.toString()).toInt() + dataDir = properties.getProperty("dataDir", dataDir) + connectionTimeout = properties.getProperty("connectionTimeout", connectionTimeout.toString()).toInt() jdbcDriver = properties.getProperty("jdbcDriver", jdbcDriver) 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 f292be7..e2ec1d7 100644 --- a/src/jvmMain/kotlin/nl/astraeus/vst/base/web/RequestHandler.kt +++ b/src/jvmMain/kotlin/nl/astraeus/vst/base/web/RequestHandler.kt @@ -17,10 +17,14 @@ 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,6 +38,57 @@ 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() @@ -78,16 +133,51 @@ class WebsocketHandler( } } - // todo: add LOAD_BINARY command which expects a hash, - // find the file in the data/files/ directory and send it as binary + "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?) { - // todo: create hash from binary, save file in data/files/ directory, + // 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) + } + } } }