Move stuff into base
This commit is contained in:
@@ -39,13 +39,13 @@ kotlin {
|
|||||||
//base
|
//base
|
||||||
api("nl.astraeus:kotlin-css-generator:1.0.7")
|
api("nl.astraeus:kotlin-css-generator:1.0.7")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
|
||||||
|
implementation("nl.astraeus:vst-ui-base:1.1.0-SNAPSHOT")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val jsMain by getting {
|
val jsMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
//base
|
//base
|
||||||
implementation("nl.astraeus:kotlin-komponent-js:1.2.2")
|
implementation("nl.astraeus:kotlin-komponent-js:1.2.2")
|
||||||
implementation("nl.astraeus:vst-ui-base:1.0.0-SNAPSHOT")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val jsTest by getting {
|
val jsTest by getting {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import kotlin.math.round
|
|||||||
|
|
||||||
expect fun randomDouble(): Double
|
expect fun randomDouble(): Double
|
||||||
|
|
||||||
|
const val BUFFER_MULTIPLY = 4
|
||||||
|
|
||||||
@ExperimentalJsExport
|
@ExperimentalJsExport
|
||||||
@JsExport
|
@JsExport
|
||||||
class PhysicalString(
|
class PhysicalString(
|
||||||
@@ -14,9 +16,9 @@ class PhysicalString(
|
|||||||
var damping: Double,
|
var damping: Double,
|
||||||
) {
|
) {
|
||||||
val sampleLength = 1.0 / sampleRate.toDouble()
|
val sampleLength = 1.0 / sampleRate.toDouble()
|
||||||
val maxLength = sampleRate / Note.G9.freq
|
val maxLength = sampleRate / Note.NO01.freq
|
||||||
var length = 1
|
var length = 1
|
||||||
val buffer = Array(maxLength.toInt() + 1) { 0.0 }
|
val buffer = Array((maxLength * BUFFER_MULTIPLY).toInt() + 1) { 0.0 }
|
||||||
var sample = 0
|
var sample = 0
|
||||||
var index = 0
|
var index = 0
|
||||||
var remaining = 0.0
|
var remaining = 0.0
|
||||||
@@ -26,7 +28,7 @@ class PhysicalString(
|
|||||||
fun pluck(note: Note, velocity: Double, smoothing: Int = 0) {
|
fun pluck(note: Note, velocity: Double, smoothing: Int = 0) {
|
||||||
available = false
|
available = false
|
||||||
currentNote = note
|
currentNote = note
|
||||||
length = round(sampleRate / note.freq).toInt()
|
length = round(BUFFER_MULTIPLY * sampleRate / note.freq).toInt()
|
||||||
sample = 0
|
sample = 0
|
||||||
index = 0
|
index = 0
|
||||||
|
|
||||||
@@ -37,7 +39,7 @@ class PhysicalString(
|
|||||||
buffer[i] = -randomDouble() * velocity
|
buffer[i] = -randomDouble() * velocity
|
||||||
}
|
}
|
||||||
//buffer[i] = (randomDouble() - 0.5) * 2.0 * velocity
|
//buffer[i] = (randomDouble() - 0.5) * 2.0 * velocity
|
||||||
//buffer[i] = sin(PI * 2 * i/length)
|
//buffer[i] = sin(PI * 2 * i/length) * randomDouble() * velocity
|
||||||
//buffer[i] = (i/length.toDouble() * 2.0) - 1.0 //if (i < length / 2) { 1.0 } else { -1.0 }
|
//buffer[i] = (i/length.toDouble() * 2.0) - 1.0 //if (i < length / 2) { 1.0 } else { -1.0 }
|
||||||
}
|
}
|
||||||
repeat(smoothing) {
|
repeat(smoothing) {
|
||||||
@@ -58,18 +60,20 @@ class PhysicalString(
|
|||||||
fun tick(): Double {
|
fun tick(): Double {
|
||||||
val result = buffer[index]
|
val result = buffer[index]
|
||||||
|
|
||||||
var newValue = 0.0
|
repeat(BUFFER_MULTIPLY) {
|
||||||
newValue += getValueFromBuffer(index + 1) * 0.2
|
var newValue = 0.0
|
||||||
newValue += getValueFromBuffer(index + 2) * 0.3
|
newValue += getValueFromBuffer(index + 1) * 0.1
|
||||||
newValue += getValueFromBuffer(index + 3) * 0.3
|
newValue += getValueFromBuffer(index + 2) * 0.15
|
||||||
newValue += getValueFromBuffer(index + 4) * 0.2
|
newValue += getValueFromBuffer(index + 3) * 0.25
|
||||||
// newValue += getValueFromBuffer(index + 5) * 0.2
|
newValue += getValueFromBuffer(index + 4) * 0.25
|
||||||
// newValue += getValueFromBuffer(index + 6) * 0.3
|
newValue += getValueFromBuffer(index + 5) * 0.15
|
||||||
newValue *= damping
|
newValue += getValueFromBuffer(index + 6) * 0.1
|
||||||
|
newValue *= damping
|
||||||
|
|
||||||
buffer[index] = newValue
|
buffer[index] = newValue
|
||||||
|
|
||||||
index = (index + 1) % length
|
index = (index + 1) % length
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
package nl.astraeus.vst.string.logger
|
|
||||||
|
|
||||||
val log = Logger
|
|
||||||
|
|
||||||
enum class LogLevel {
|
|
||||||
TRACE,
|
|
||||||
DEBUG,
|
|
||||||
INFO,
|
|
||||||
WARN,
|
|
||||||
ERROR,
|
|
||||||
FATAL
|
|
||||||
}
|
|
||||||
|
|
||||||
object Logger {
|
|
||||||
var level: LogLevel = LogLevel.INFO
|
|
||||||
|
|
||||||
fun trace(message: () -> String?) {
|
|
||||||
if (level.ordinal <= LogLevel.TRACE.ordinal) {
|
|
||||||
println("TRACE: ${message()}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun debug(message: () -> String?) {
|
|
||||||
if (level.ordinal <= LogLevel.DEBUG.ordinal) {
|
|
||||||
println("DEBUG: ${message()}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun info(message: () -> String?) {
|
|
||||||
if (level.ordinal <= LogLevel.INFO.ordinal) {
|
|
||||||
println("INFO: ${message()}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun warn(e: Throwable? = null, message: () -> String?) {
|
|
||||||
if (level.ordinal <= LogLevel.WARN.ordinal) {
|
|
||||||
println("WARN: ${message()}")
|
|
||||||
e?.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun error(e: Throwable? = null, message: () -> String?) {
|
|
||||||
if (level.ordinal <= LogLevel.ERROR.ordinal) {
|
|
||||||
println("ERROR: ${message()}")
|
|
||||||
e?.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fatal(e: Throwable, message: () -> String?) {
|
|
||||||
println("FATAL: ${message()}")
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,18 +3,24 @@ package nl.astraeus.vst.string
|
|||||||
import kotlinx.browser.document
|
import kotlinx.browser.document
|
||||||
import nl.astraeus.komp.Komponent
|
import nl.astraeus.komp.Komponent
|
||||||
import nl.astraeus.komp.UnsafeMode
|
import nl.astraeus.komp.UnsafeMode
|
||||||
|
import nl.astraeus.vst.string.audio.VstStringWorklet
|
||||||
import nl.astraeus.vst.string.logger.log
|
import nl.astraeus.vst.string.logger.log
|
||||||
import nl.astraeus.vst.string.midi.Midi
|
import nl.astraeus.vst.string.midi.Midi
|
||||||
import nl.astraeus.vst.string.view.MainView
|
import nl.astraeus.vst.string.view.MainView
|
||||||
import nl.astraeus.vst.string.ws.WebsocketClient
|
import nl.astraeus.vst.string.ws.WebsocketClient
|
||||||
import nl.astraeus.vst.ui.css.CssSettings
|
import nl.astraeus.vst.ui.css.CssSettings
|
||||||
|
import nl.astraeus.vst.ui.view.BaseVstView
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
CssSettings.shortId = false
|
CssSettings.shortId = false
|
||||||
CssSettings.preFix = "vst-chip"
|
CssSettings.preFix = "vst-chip"
|
||||||
|
|
||||||
Komponent.unsafeMode = UnsafeMode.UNSAFE_SVG_ONLY
|
Komponent.unsafeMode = UnsafeMode.UNSAFE_SVG_ONLY
|
||||||
Komponent.create(document.body!!, MainView)
|
Komponent.create(document.body!!, BaseVstView("VST Guiter", MainView) {
|
||||||
|
VstStringWorklet.create {
|
||||||
|
WebsocketClient.send("LOAD\n")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
Midi.start()
|
Midi.start()
|
||||||
|
|
||||||
|
|||||||
@@ -18,15 +18,10 @@ import nl.astraeus.css.properties.Display
|
|||||||
import nl.astraeus.css.properties.FlexDirection
|
import nl.astraeus.css.properties.FlexDirection
|
||||||
import nl.astraeus.css.properties.FontWeight
|
import nl.astraeus.css.properties.FontWeight
|
||||||
import nl.astraeus.css.properties.JustifyContent
|
import nl.astraeus.css.properties.JustifyContent
|
||||||
import nl.astraeus.css.properties.Position
|
|
||||||
import nl.astraeus.css.properties.Transform
|
|
||||||
import nl.astraeus.css.properties.em
|
import nl.astraeus.css.properties.em
|
||||||
import nl.astraeus.css.properties.hsla
|
|
||||||
import nl.astraeus.css.properties.prc
|
import nl.astraeus.css.properties.prc
|
||||||
import nl.astraeus.css.properties.px
|
import nl.astraeus.css.properties.px
|
||||||
import nl.astraeus.css.properties.rem
|
import nl.astraeus.css.properties.rem
|
||||||
import nl.astraeus.css.properties.vh
|
|
||||||
import nl.astraeus.css.properties.vw
|
|
||||||
import nl.astraeus.css.style.Style
|
import nl.astraeus.css.style.Style
|
||||||
import nl.astraeus.css.style.cls
|
import nl.astraeus.css.style.cls
|
||||||
import nl.astraeus.komp.HtmlBuilder
|
import nl.astraeus.komp.HtmlBuilder
|
||||||
@@ -48,7 +43,6 @@ import org.w3c.dom.HTMLSelectElement
|
|||||||
|
|
||||||
object MainView : Komponent(), CssName {
|
object MainView : Komponent(), CssName {
|
||||||
private var messages: MutableList<String> = ArrayList()
|
private var messages: MutableList<String> = ArrayList()
|
||||||
var started = false
|
|
||||||
val playString = PhysicalStringView(
|
val playString = PhysicalStringView(
|
||||||
PhysicalString(
|
PhysicalString(
|
||||||
sampleRate = 48000,
|
sampleRate = 48000,
|
||||||
@@ -70,22 +64,6 @@ object MainView : Komponent(), CssName {
|
|||||||
|
|
||||||
override fun HtmlBuilder.render() {
|
override fun HtmlBuilder.render() {
|
||||||
div(MainDivCss.name) {
|
div(MainDivCss.name) {
|
||||||
if (!started) {
|
|
||||||
div(StartSplashCss.name) {
|
|
||||||
div(StartBoxCss.name) {
|
|
||||||
div(StartButtonCss.name) {
|
|
||||||
+"START"
|
|
||||||
onClickFunction = {
|
|
||||||
VstStringWorklet.create {
|
|
||||||
started = true
|
|
||||||
requestUpdate()
|
|
||||||
WebsocketClient.send("LOAD\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h1 {
|
h1 {
|
||||||
+"VST Guitar"
|
+"VST Guitar"
|
||||||
}
|
}
|
||||||
@@ -175,9 +153,6 @@ object MainView : Komponent(), CssName {
|
|||||||
object ButtonBarCss : CssName
|
object ButtonBarCss : CssName
|
||||||
object SelectedCss : CssName
|
object SelectedCss : CssName
|
||||||
object NoteBarCss : CssName
|
object NoteBarCss : CssName
|
||||||
object StartSplashCss : CssName
|
|
||||||
object StartBoxCss : CssName
|
|
||||||
object StartButtonCss : CssName
|
|
||||||
object ControlsCss : CssName
|
object ControlsCss : CssName
|
||||||
|
|
||||||
private fun css() {
|
private fun css() {
|
||||||
@@ -239,36 +214,6 @@ object MainView : Komponent(), CssName {
|
|||||||
color(Css.currentStyle.mainFontColor)
|
color(Css.currentStyle.mainFontColor)
|
||||||
borderRadius(0.25.em)
|
borderRadius(0.25.em)
|
||||||
}
|
}
|
||||||
select(cls(StartSplashCss)) {
|
|
||||||
position(Position.fixed)
|
|
||||||
left(0.px)
|
|
||||||
top(0.px)
|
|
||||||
width(100.vw)
|
|
||||||
height(100.vh)
|
|
||||||
zIndex(100)
|
|
||||||
backgroundColor(hsla(32, 0, 5, 0.65))
|
|
||||||
|
|
||||||
select(cls(StartBoxCss)) {
|
|
||||||
position(Position.relative)
|
|
||||||
left(25.vw)
|
|
||||||
top(25.vh)
|
|
||||||
width(50.vw)
|
|
||||||
height(50.vh)
|
|
||||||
backgroundColor(hsla(239, 50, 10, 1.0))
|
|
||||||
borderColor(Css.currentStyle.mainFontColor)
|
|
||||||
borderWidth(2.px)
|
|
||||||
|
|
||||||
select(cls(StartButtonCss)) {
|
|
||||||
position(Position.absolute)
|
|
||||||
left(50.prc)
|
|
||||||
top(50.prc)
|
|
||||||
transform(Transform("translate(-50%, -50%)"))
|
|
||||||
padding(1.rem)
|
|
||||||
backgroundColor(Css.currentStyle.buttonBackgroundColor)
|
|
||||||
cursor("pointer")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
select(ControlsCss.cls()) {
|
select(ControlsCss.cls()) {
|
||||||
display(Display.flex)
|
display(Display.flex)
|
||||||
flexDirection(FlexDirection.row)
|
flexDirection(FlexDirection.row)
|
||||||
|
|||||||
@@ -40,9 +40,7 @@ class PhysicalStringView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun onAnimationFrame(time: Double) {
|
private fun onAnimationFrame(time: Double) {
|
||||||
if (MainView.started) {
|
draw()
|
||||||
draw()
|
|
||||||
}
|
|
||||||
|
|
||||||
window.requestAnimationFrame(::onAnimationFrame)
|
window.requestAnimationFrame(::onAnimationFrame)
|
||||||
}
|
}
|
||||||
@@ -89,7 +87,7 @@ class PhysicalStringView(
|
|||||||
|
|
||||||
private fun draw() {
|
private fun draw() {
|
||||||
val ctx = context
|
val ctx = context
|
||||||
if (ctx != null) {
|
if (ctx != null && ctx.canvas.isConnected) {
|
||||||
val width = ctx.canvas.width.toDouble()
|
val width = ctx.canvas.width.toDouble()
|
||||||
val height = ctx.canvas.height.toDouble()
|
val height = ctx.canvas.height.toDouble()
|
||||||
val halfHeight = height / 2.0
|
val halfHeight = height / 2.0
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ object WaveformView : Komponent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onAnimationFrame(time: Double) {
|
fun onAnimationFrame(time: Double) {
|
||||||
if (MainView.started) {
|
if (VstStringWorklet.recording == null) {
|
||||||
VstStringWorklet.postMessage("start_recording")
|
VstStringWorklet.postMessage("start_recording")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +50,7 @@ object WaveformView : Komponent() {
|
|||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
VstStringWorklet.recording = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
package nl.astraeus.vst.string
|
|
||||||
|
|
||||||
import java.security.SecureRandom
|
|
||||||
|
|
||||||
val idChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
||||||
val random = SecureRandom()
|
|
||||||
|
|
||||||
fun generateId(): String {
|
|
||||||
val id = StringBuilder()
|
|
||||||
|
|
||||||
for (i in 0 until 8) {
|
|
||||||
id.append(idChars[random.nextInt(idChars.length)])
|
|
||||||
}
|
|
||||||
|
|
||||||
return id.toString()
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,10 @@
|
|||||||
package nl.astraeus.vst.string
|
package nl.astraeus.vst.string
|
||||||
|
|
||||||
import com.zaxxer.hikari.HikariConfig
|
import nl.astraeus.vst.base.Settings
|
||||||
import io.undertow.Undertow
|
import nl.astraeus.vst.base.db.Database
|
||||||
import io.undertow.UndertowOptions
|
import nl.astraeus.vst.base.web.UndertowServer
|
||||||
import io.undertow.server.session.InMemorySessionManager
|
|
||||||
import io.undertow.server.session.SessionAttachmentHandler
|
|
||||||
import io.undertow.server.session.SessionCookieConfig
|
|
||||||
import nl.astraeus.vst.string.db.Database
|
|
||||||
import nl.astraeus.vst.string.logger.LogLevel
|
import nl.astraeus.vst.string.logger.LogLevel
|
||||||
import nl.astraeus.vst.string.logger.Logger
|
import nl.astraeus.vst.string.logger.Logger
|
||||||
import nl.astraeus.vst.string.web.RequestHandler
|
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
Logger.level = LogLevel.DEBUG
|
Logger.level = LogLevel.DEBUG
|
||||||
@@ -18,32 +13,13 @@ fun main() {
|
|||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
||||||
Class.forName("nl.astraeus.jdbc.Driver")
|
Settings.port = 9004
|
||||||
|
Settings.jdbcStatsPort = 6004
|
||||||
|
|
||||||
Database.initialize(HikariConfig().apply {
|
Database.start()
|
||||||
driverClassName = "nl.astraeus.jdbc.Driver"
|
|
||||||
jdbcUrl = "jdbc:stat:webServerPort=6002:jdbc:sqlite:data/chip.db"
|
|
||||||
username = "sa"
|
|
||||||
password = ""
|
|
||||||
maximumPoolSize = 25
|
|
||||||
isAutoCommit = false
|
|
||||||
|
|
||||||
validate()
|
UndertowServer.start(
|
||||||
})
|
"Vst String",
|
||||||
|
"/vst-string-worklet-ui.js"
|
||||||
val sessionHandler = SessionAttachmentHandler(
|
|
||||||
InMemorySessionManager("vst-session-manager"),
|
|
||||||
SessionCookieConfig()
|
|
||||||
)
|
)
|
||||||
sessionHandler.setNext(RequestHandler)
|
|
||||||
|
|
||||||
val server = Undertow.builder()
|
|
||||||
.addHttpListener(Settings.port, "localhost")
|
|
||||||
.setIoThreads(4)
|
|
||||||
.setHandler(sessionHandler)
|
|
||||||
.setServerOption(UndertowOptions.SHUTDOWN_TIMEOUT, 1000)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
println("Starting server at port ${Settings.port}...")
|
|
||||||
server?.start()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
package nl.astraeus.vst.string
|
|
||||||
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
object Settings {
|
|
||||||
var runningAsRoot: Boolean = false
|
|
||||||
var port = 9004
|
|
||||||
var sslPort = 8443
|
|
||||||
var connectionTimeout = 30000
|
|
||||||
|
|
||||||
var jdbcDriver = "nl.astraeus.jdbc.Driver"
|
|
||||||
var jdbcConnectionUrl = "jdbc:stat:webServerPort=6001:jdbc:sqlite:data/srp.db"
|
|
||||||
var jdbcUser = "sa"
|
|
||||||
var jdbcPassword = ""
|
|
||||||
|
|
||||||
var adminUser = "rnentjes"
|
|
||||||
var adminPassword = "9/SG_Bd}9gWz~?j\\A.U]n9]OO"
|
|
||||||
|
|
||||||
fun getPropertiesFromFile(filename: String): Properties? {
|
|
||||||
val propertiesFile = File(filename)
|
|
||||||
return if (propertiesFile.exists()) {
|
|
||||||
val properties = Properties()
|
|
||||||
FileInputStream(propertiesFile).use {
|
|
||||||
properties.load(it)
|
|
||||||
}
|
|
||||||
properties
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun readProperties(args: Array<String>) {
|
|
||||||
val filename = if (args.isNotEmpty()) args[0] else "srp.properties"
|
|
||||||
val properties = getPropertiesFromFile(filename) ?: return // return if properties couldn't be loaded
|
|
||||||
|
|
||||||
runningAsRoot = properties.getProperty("runningAsRoot", runningAsRoot.toString()).toBoolean()
|
|
||||||
port = properties.getProperty("port", port.toString()).toInt()
|
|
||||||
sslPort = properties.getProperty("sslPort", sslPort.toString()).toInt()
|
|
||||||
connectionTimeout = properties.getProperty("connectionTimeout", connectionTimeout.toString()).toInt()
|
|
||||||
jdbcDriver = properties.getProperty("jdbcDriver", jdbcDriver)
|
|
||||||
jdbcConnectionUrl = properties.getProperty("jdbcConnectionUrl", jdbcConnectionUrl)
|
|
||||||
jdbcUser = properties.getProperty("jdbcUser", jdbcUser)
|
|
||||||
jdbcPassword = properties.getProperty("jdbcPassword", jdbcPassword)
|
|
||||||
|
|
||||||
adminUser = properties.getProperty("adminUser", adminUser)
|
|
||||||
adminPassword = properties.getProperty("adminPassword", adminPassword)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
package nl.astraeus.vst.string.db
|
|
||||||
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
import nl.astraeus.vst.string.logger.log
|
|
||||||
import java.sql.PreparedStatement
|
|
||||||
import java.sql.ResultSet
|
|
||||||
import java.sql.Timestamp
|
|
||||||
|
|
||||||
fun Instant.toSqlTimestamp() = Timestamp(this.toEpochMilliseconds())
|
|
||||||
fun Timestamp.toDateTimeInstant() = Instant.fromEpochMilliseconds(this.time)
|
|
||||||
|
|
||||||
data class SqlStatement<T : Entity>(
|
|
||||||
val sql: String,
|
|
||||||
val prepareParameters: T.(PreparedStatement) -> Unit
|
|
||||||
)
|
|
||||||
|
|
||||||
data class SqlQuery<T : Entity>(
|
|
||||||
val sql: String,
|
|
||||||
val resultMapper: (ResultSet) -> T
|
|
||||||
)
|
|
||||||
|
|
||||||
abstract class QueryProvider<T : Entity> {
|
|
||||||
abstract val tableName: String
|
|
||||||
open val idQuery: String
|
|
||||||
get() = "SELECT * FROM $tableName WHERE ID = ?"
|
|
||||||
abstract val resultSetMapper: (ResultSet) -> T
|
|
||||||
open val find: SqlQuery<T>
|
|
||||||
get() = SqlQuery(
|
|
||||||
idQuery,
|
|
||||||
resultSetMapper
|
|
||||||
)
|
|
||||||
abstract val insert: SqlStatement<T>
|
|
||||||
abstract val update: SqlStatement<T>
|
|
||||||
open val delete: SqlStatement<T>
|
|
||||||
get() = SqlStatement(
|
|
||||||
"DELETE FROM $tableName WHERE ID = ?"
|
|
||||||
) { ps ->
|
|
||||||
ps.setLong(1, getPK()[0] as Long)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class BaseDao<T : Entity> {
|
|
||||||
abstract val queryProvider: QueryProvider<T>
|
|
||||||
open val autogeneratedPrimaryKey: Boolean = true
|
|
||||||
|
|
||||||
open fun insert(entity: T) {
|
|
||||||
executeInsert(entity, "insert", queryProvider.insert)
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun update(entity: T): Int = executeUpdate(
|
|
||||||
entity,
|
|
||||||
"update",
|
|
||||||
queryProvider.update,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
|
|
||||||
open fun upsert(entity: T) {
|
|
||||||
if ((entity.getPK()[0] as Long) == 0L) {
|
|
||||||
insert(entity)
|
|
||||||
} else {
|
|
||||||
update(entity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun delete(entity: T) {
|
|
||||||
executeUpdate(entity, "delete", queryProvider.delete, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun find(
|
|
||||||
id: Long
|
|
||||||
): T? {
|
|
||||||
return executeQuery(
|
|
||||||
"find",
|
|
||||||
queryProvider.find
|
|
||||||
) { ps ->
|
|
||||||
ps.setLong(1, id)
|
|
||||||
}.firstOrNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun executeSQLUpdate(
|
|
||||||
sql: String,
|
|
||||||
parameterSetter: (PreparedStatement) -> Unit
|
|
||||||
): Int {
|
|
||||||
return transaction { con ->
|
|
||||||
con.prepareStatement(sql).use { ps ->
|
|
||||||
parameterSetter(ps)
|
|
||||||
|
|
||||||
ps.executeUpdate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun executeQuery(
|
|
||||||
label: String,
|
|
||||||
statement: SqlQuery<T>,
|
|
||||||
prepareParameters: (PreparedStatement) -> Unit,
|
|
||||||
): List<T> {
|
|
||||||
return transaction { con ->
|
|
||||||
log.debug { "Executing query [$label] - [${statement.sql}]" }
|
|
||||||
val result = mutableListOf<T>()
|
|
||||||
|
|
||||||
con.prepareStatement(statement.sql)?.use { ps ->
|
|
||||||
prepareParameters(ps)
|
|
||||||
|
|
||||||
val rs = ps.executeQuery()
|
|
||||||
|
|
||||||
while (rs.next()) {
|
|
||||||
result.add(statement.resultMapper(rs))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun executeInsert(
|
|
||||||
entity: T,
|
|
||||||
label: String,
|
|
||||||
statement: SqlStatement<T>,
|
|
||||||
checkSingleRow: Boolean = false
|
|
||||||
) {
|
|
||||||
transaction { con ->
|
|
||||||
log.debug { "Executing insert [$label] - [${statement.sql}] - [$entity]" }
|
|
||||||
con.prepareStatement(statement.sql)?.use { ps ->
|
|
||||||
statement.prepareParameters(entity, ps)
|
|
||||||
|
|
||||||
val rows = if (checkSingleRow) {
|
|
||||||
ps.execute()
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
ps.executeUpdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (autogeneratedPrimaryKey) {
|
|
||||||
val keyResult = ps.generatedKeys
|
|
||||||
if (keyResult.next()) {
|
|
||||||
entity.setPK(arrayOf(keyResult.getLong(1)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
check(rows == 1) {
|
|
||||||
"Statement [$label] affected more than 1 row! [${statement.sql}]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun executeUpdate(
|
|
||||||
entity: T,
|
|
||||||
label: String,
|
|
||||||
statement: SqlStatement<T>,
|
|
||||||
checkSingleRow: Boolean = false
|
|
||||||
): Int = transaction { con ->
|
|
||||||
var rows = 1
|
|
||||||
|
|
||||||
log.debug { "Executing update [$label] - [${statement.sql}] - [$entity]" }
|
|
||||||
con.prepareStatement(statement.sql)?.use { ps ->
|
|
||||||
statement.prepareParameters(entity, ps)
|
|
||||||
|
|
||||||
rows = ps.executeUpdate()
|
|
||||||
|
|
||||||
check(checkSingleRow || rows == 1) {
|
|
||||||
"Statement [$label] affected more than 1 row! [${statement.sql}]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rows
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
package nl.astraeus.vst.string.db
|
|
||||||
|
|
||||||
import com.zaxxer.hikari.HikariConfig
|
|
||||||
import com.zaxxer.hikari.HikariDataSource
|
|
||||||
import java.sql.Connection
|
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
|
|
||||||
enum class TxScope {
|
|
||||||
REQUIRED,
|
|
||||||
/* if needed we need to switch db, sqlite only allows one writer/connection */
|
|
||||||
//REQUIRES_NEW
|
|
||||||
}
|
|
||||||
|
|
||||||
private val currentConnection = ThreadLocal<Connection>()
|
|
||||||
|
|
||||||
fun <T> transaction(
|
|
||||||
scope: TxScope = TxScope.REQUIRED,
|
|
||||||
block: (Connection) -> T
|
|
||||||
): T {
|
|
||||||
val hasConnection = currentConnection.get() != null
|
|
||||||
var oldConnection: Connection? = null
|
|
||||||
|
|
||||||
if (!hasConnection) {
|
|
||||||
currentConnection.set(Database.getConnection())
|
|
||||||
/*
|
|
||||||
} else if (scope == TxScope.REQUIRES_NEW) {
|
|
||||||
oldConnection = currentConnection.get()
|
|
||||||
|
|
||||||
currentConnection.set(Database.getConnection())
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
val connection = currentConnection.get()
|
|
||||||
|
|
||||||
try {
|
|
||||||
val result = block(connection)
|
|
||||||
|
|
||||||
connection.commit()
|
|
||||||
|
|
||||||
return result
|
|
||||||
} finally {
|
|
||||||
if (!hasConnection) {
|
|
||||||
currentConnection.set(oldConnection)
|
|
||||||
connection.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object Database {
|
|
||||||
|
|
||||||
var ds: HikariDataSource? = null
|
|
||||||
|
|
||||||
fun initialize(config: HikariConfig) {
|
|
||||||
val properties = Properties()
|
|
||||||
properties["journal_mode"] = "WAL"
|
|
||||||
|
|
||||||
|
|
||||||
config.dataSourceProperties = properties
|
|
||||||
config.addDataSourceProperty("cachePrepStmts", "true")
|
|
||||||
config.addDataSourceProperty("prepStmtCacheSize", "250")
|
|
||||||
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
|
|
||||||
|
|
||||||
ds = HikariDataSource(config)
|
|
||||||
Migrations.databaseVersionTableCreated = AtomicBoolean(false)
|
|
||||||
Migrations.updateDatabaseIfNeeded()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getConnection() = ds?.connection ?: error("Database has not been initialized!")
|
|
||||||
|
|
||||||
/*
|
|
||||||
val ds: HikariDataSource
|
|
||||||
|
|
||||||
init {
|
|
||||||
val properties = Properties()
|
|
||||||
properties["journal_mode"] = "WAL"
|
|
||||||
|
|
||||||
val config = HikariConfig().apply {
|
|
||||||
driverClassName = "nl.astraeus.jdbc.Driver"
|
|
||||||
jdbcUrl = "jdbc:stat:webServerPort=6001:jdbc:sqlite:data/daw3.db"
|
|
||||||
username = "sa"
|
|
||||||
password = ""
|
|
||||||
maximumPoolSize = 25
|
|
||||||
isAutoCommit = false
|
|
||||||
dataSourceProperties = properties
|
|
||||||
validate()
|
|
||||||
}
|
|
||||||
|
|
||||||
config.addDataSourceProperty("cachePrepStmts", "true")
|
|
||||||
config.addDataSourceProperty("prepStmtCacheSize", "250")
|
|
||||||
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
|
|
||||||
|
|
||||||
ds = HikariDataSource(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getConnection() = ds.connection
|
|
||||||
*/
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package nl.astraeus.vst.string.db
|
|
||||||
|
|
||||||
interface Entity {
|
|
||||||
fun getPK(): Array<Any>
|
|
||||||
fun setPK(pks: Array<Any>)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EntityId : Entity {
|
|
||||||
var id: Long
|
|
||||||
|
|
||||||
override fun getPK(): Array<Any> = arrayOf(id)
|
|
||||||
|
|
||||||
override fun setPK(pks: Array<Any>) {
|
|
||||||
id = pks[0] as Long
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
package nl.astraeus.vst.string.db
|
|
||||||
|
|
||||||
import nl.astraeus.vst.string.logger.log
|
|
||||||
import java.sql.Connection
|
|
||||||
import java.sql.SQLException
|
|
||||||
import java.sql.Timestamp
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
|
|
||||||
sealed class Migration {
|
|
||||||
class Query(
|
|
||||||
val query: String
|
|
||||||
) : Migration() {
|
|
||||||
override fun toString(): String {
|
|
||||||
return query
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Code(
|
|
||||||
val code: (Connection) -> Unit
|
|
||||||
) : Migration() {
|
|
||||||
override fun toString(): String {
|
|
||||||
return code.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val DATABASE_MIGRATIONS = arrayOf<Migration>(
|
|
||||||
Migration.Query(
|
|
||||||
"""
|
|
||||||
CREATE TABLE DATABASE_VERSION (
|
|
||||||
ID INTEGER PRIMARY KEY,
|
|
||||||
QUERY TEXT,
|
|
||||||
EXECUTED TIMESTAMP
|
|
||||||
)
|
|
||||||
""".trimIndent()
|
|
||||||
),
|
|
||||||
Migration.Query(PATCH_CREATE_QUERY),
|
|
||||||
)
|
|
||||||
|
|
||||||
object Migrations {
|
|
||||||
var databaseVersionTableCreated = AtomicBoolean(false)
|
|
||||||
|
|
||||||
fun updateDatabaseIfNeeded() {
|
|
||||||
try {
|
|
||||||
transaction { con ->
|
|
||||||
con.prepareStatement(
|
|
||||||
"""
|
|
||||||
SELECT MAX(ID) FROM DATABASE_VERSION
|
|
||||||
""".trimIndent()
|
|
||||||
).use { ps ->
|
|
||||||
ps.executeQuery().use { rs ->
|
|
||||||
databaseVersionTableCreated.compareAndSet(false, true)
|
|
||||||
|
|
||||||
if (rs.next()) {
|
|
||||||
val maxId = rs.getInt(1)
|
|
||||||
|
|
||||||
for (index in maxId + 1..<DATABASE_MIGRATIONS.size) {
|
|
||||||
executeMigration(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: SQLException) {
|
|
||||||
if (databaseVersionTableCreated.compareAndSet(false, true)) {
|
|
||||||
executeMigration(0)
|
|
||||||
updateDatabaseIfNeeded()
|
|
||||||
} else {
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun executeMigration(index: Int) {
|
|
||||||
transaction { con ->
|
|
||||||
log.debug {
|
|
||||||
"Executing migration $index - [${DATABASE_MIGRATIONS[index]}]"
|
|
||||||
}
|
|
||||||
val description = when (
|
|
||||||
val migration = DATABASE_MIGRATIONS[index]
|
|
||||||
) {
|
|
||||||
is Migration.Query -> {
|
|
||||||
con.prepareStatement(migration.query).use { ps ->
|
|
||||||
ps.execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
migration.query
|
|
||||||
}
|
|
||||||
|
|
||||||
is Migration.Code -> {
|
|
||||||
migration.code(con)
|
|
||||||
|
|
||||||
migration.code.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
con.prepareStatement("INSERT INTO DATABASE_VERSION VALUES (?, ?, ?)").use { ps ->
|
|
||||||
ps.setInt(1, index)
|
|
||||||
ps.setString(2, description)
|
|
||||||
ps.setTimestamp(3, Timestamp(System.currentTimeMillis()))
|
|
||||||
|
|
||||||
ps.execute()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package nl.astraeus.vst.string.db
|
|
||||||
|
|
||||||
object PatchDao : BaseDao<PatchEntity>() {
|
|
||||||
|
|
||||||
override val queryProvider: QueryProvider<PatchEntity>
|
|
||||||
get() = PatchEntityQueryProvider
|
|
||||||
|
|
||||||
fun create(
|
|
||||||
patchId: String,
|
|
||||||
patch: String
|
|
||||||
): PatchEntity {
|
|
||||||
val result = PatchEntity(
|
|
||||||
0,
|
|
||||||
patchId,
|
|
||||||
patch
|
|
||||||
)
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
fun findById(patchId: String): PatchEntity? = executeQuery(
|
|
||||||
"findById",
|
|
||||||
SqlQuery(
|
|
||||||
"SELECT * FROM ${queryProvider.tableName} WHERE PATCH_ID = ?",
|
|
||||||
queryProvider.resultSetMapper
|
|
||||||
)
|
|
||||||
) { ps ->
|
|
||||||
ps.setString(1, patchId)
|
|
||||||
}.firstOrNull()
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package nl.astraeus.vst.string.db
|
|
||||||
|
|
||||||
import kotlinx.datetime.Clock
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
|
|
||||||
data class PatchEntity(
|
|
||||||
override var id: Long,
|
|
||||||
var patchId: String,
|
|
||||||
var patch: String,
|
|
||||||
var created: Instant = Clock.System.now(),
|
|
||||||
var updated: Instant = Clock.System.now(),
|
|
||||||
) : EntityId
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
package nl.astraeus.vst.string.db
|
|
||||||
|
|
||||||
import java.sql.ResultSet
|
|
||||||
import java.sql.Types
|
|
||||||
|
|
||||||
val PATCH_CREATE_QUERY = """
|
|
||||||
CREATE TABLE INSTRUMENTS (
|
|
||||||
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
PATCH_ID TEXT,
|
|
||||||
PATCH TEXT,
|
|
||||||
CREATED TIMESTAMP,
|
|
||||||
UPDATED TIMESTAMP
|
|
||||||
)
|
|
||||||
""".trimIndent()
|
|
||||||
|
|
||||||
object PatchEntityQueryProvider : QueryProvider<PatchEntity>() {
|
|
||||||
override val tableName: String
|
|
||||||
get() = "INSTRUMENTS"
|
|
||||||
override val resultSetMapper: (ResultSet) -> PatchEntity
|
|
||||||
get() = { rs ->
|
|
||||||
PatchEntity(
|
|
||||||
rs.getLong(1),
|
|
||||||
rs.getString(2),
|
|
||||||
rs.getString(3),
|
|
||||||
rs.getTimestamp(4).toDateTimeInstant(),
|
|
||||||
rs.getTimestamp(5).toDateTimeInstant()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
override val insert: SqlStatement<PatchEntity>
|
|
||||||
get() = SqlStatement(
|
|
||||||
"""
|
|
||||||
INSERT INTO $tableName (
|
|
||||||
ID,
|
|
||||||
PATCH_ID,
|
|
||||||
PATCH,
|
|
||||||
CREATED,
|
|
||||||
UPDATED
|
|
||||||
) VALUES (
|
|
||||||
?,?,?,?,?
|
|
||||||
)
|
|
||||||
""".trimIndent()
|
|
||||||
) { ps ->
|
|
||||||
ps.setNull(1, Types.BIGINT)
|
|
||||||
ps.setString(2, patchId)
|
|
||||||
ps.setString(3, patch)
|
|
||||||
ps.setTimestamp(4, created.toSqlTimestamp())
|
|
||||||
ps.setTimestamp(5, updated.toSqlTimestamp())
|
|
||||||
}
|
|
||||||
override val update: SqlStatement<PatchEntity>
|
|
||||||
get() = SqlStatement(
|
|
||||||
"""
|
|
||||||
UPDATE $tableName
|
|
||||||
SET PATCH_ID = ?,
|
|
||||||
PATCH = ?,
|
|
||||||
UPDATED = ?
|
|
||||||
WHERE ID = ?
|
|
||||||
""".trimIndent()
|
|
||||||
) { ps ->
|
|
||||||
ps.setString(1, patchId)
|
|
||||||
ps.setString(2, patch)
|
|
||||||
ps.setTimestamp(3, updated.toSqlTimestamp())
|
|
||||||
ps.setLong(4, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package nl.astraeus.vst.string.web
|
|
||||||
|
|
||||||
import kotlinx.html.body
|
|
||||||
import kotlinx.html.head
|
|
||||||
import kotlinx.html.html
|
|
||||||
import kotlinx.html.meta
|
|
||||||
import kotlinx.html.script
|
|
||||||
import kotlinx.html.stream.appendHTML
|
|
||||||
import kotlinx.html.title
|
|
||||||
|
|
||||||
fun generateIndex(patch: String?): String {
|
|
||||||
val result = StringBuilder();
|
|
||||||
|
|
||||||
if (patch == null) {
|
|
||||||
result.appendHTML(true).html {
|
|
||||||
head {
|
|
||||||
title { +"VST String" }
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
script {
|
|
||||||
type = "application/javascript"
|
|
||||||
src = "/vst-string-worklet-ui.js"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result.appendHTML(true).html {
|
|
||||||
head {
|
|
||||||
title { +"VST Chip" }
|
|
||||||
meta {
|
|
||||||
httpEquiv = "refresh"
|
|
||||||
content = "0; url=/patch/$patch"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
+"Redirecting to patch $patch..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.toString()
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
package nl.astraeus.vst.string.web
|
|
||||||
|
|
||||||
import io.undertow.Handlers.websocket
|
|
||||||
import io.undertow.server.HttpHandler
|
|
||||||
import io.undertow.server.HttpServerExchange
|
|
||||||
import io.undertow.server.handlers.PathHandler
|
|
||||||
import io.undertow.server.handlers.resource.PathResourceManager
|
|
||||||
import io.undertow.server.handlers.resource.ResourceHandler
|
|
||||||
import io.undertow.server.session.Session
|
|
||||||
import io.undertow.server.session.SessionConfig
|
|
||||||
import io.undertow.server.session.SessionManager
|
|
||||||
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.string.db.PatchDao
|
|
||||||
import nl.astraeus.vst.string.db.PatchEntity
|
|
||||||
import nl.astraeus.vst.string.db.transaction
|
|
||||||
import nl.astraeus.vst.string.generateId
|
|
||||||
import java.nio.file.Paths
|
|
||||||
|
|
||||||
class WebsocketHandler(
|
|
||||||
val session: Session?
|
|
||||||
) : AbstractReceiveListener(), WebSocketConnectionCallback {
|
|
||||||
|
|
||||||
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) {
|
|
||||||
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) {
|
|
||||||
transaction {
|
|
||||||
val patchEntity = PatchDao.findById(patchId)
|
|
||||||
|
|
||||||
if (patchEntity != null) {
|
|
||||||
WebSockets.sendText("LOAD\n${patchEntity.patch}", channel, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFullBinaryMessage(channel: WebSocketChannel?, message: BufferedBinaryMessage?) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object WebsocketConnectHandler : HttpHandler {
|
|
||||||
override fun handleRequest(exchange: HttpServerExchange) {
|
|
||||||
val sessionManager = exchange.getAttachment(SessionManager.ATTACHMENT_KEY)
|
|
||||||
val sessionConfig = exchange.getAttachment(SessionConfig.ATTACHMENT_KEY)
|
|
||||||
|
|
||||||
val httpSession: Session? = sessionManager.getSession(exchange, sessionConfig)
|
|
||||||
|
|
||||||
websocket(WebsocketHandler(httpSession)).handleRequest(exchange)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object PatchHandler : HttpHandler {
|
|
||||||
override fun handleRequest(exchange: HttpServerExchange) {
|
|
||||||
if (exchange.requestPath.startsWith("/patch/")) {
|
|
||||||
val patchId = exchange.requestPath.substring(7)
|
|
||||||
val sessionManager = exchange.getAttachment(SessionManager.ATTACHMENT_KEY)
|
|
||||||
val sessionConfig = exchange.getAttachment(SessionConfig.ATTACHMENT_KEY)
|
|
||||||
var httpSession: Session? = sessionManager.getSession(exchange, sessionConfig)
|
|
||||||
|
|
||||||
if (httpSession == null) {
|
|
||||||
httpSession = sessionManager.createSession(exchange, sessionConfig)
|
|
||||||
}
|
|
||||||
httpSession?.setAttribute("html-session", VstSession(patchId))
|
|
||||||
|
|
||||||
exchange.responseSender.send(generateIndex(null))
|
|
||||||
} else {
|
|
||||||
val patchId = generateId()
|
|
||||||
|
|
||||||
exchange.responseSender.send(generateIndex(patchId))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object RequestHandler : HttpHandler {
|
|
||||||
val resourceHandler = ResourceHandler(PathResourceManager(Paths.get("web")))
|
|
||||||
val pathHandler = PathHandler(resourceHandler)
|
|
||||||
|
|
||||||
init {
|
|
||||||
pathHandler.addExactPath("/", PatchHandler)
|
|
||||||
pathHandler.addExactPath("/index.html", PatchHandler)
|
|
||||||
pathHandler.addPrefixPath("/patch", PatchHandler)
|
|
||||||
pathHandler.addExactPath("/ws", WebsocketConnectHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun handleRequest(exchange: HttpServerExchange) {
|
|
||||||
pathHandler.handleRequest(exchange)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package nl.astraeus.vst.string.web
|
|
||||||
|
|
||||||
class VstSession(
|
|
||||||
val patchId: String
|
|
||||||
)
|
|
||||||
Reference in New Issue
Block a user