Move stuff into base

This commit is contained in:
2024-08-09 19:55:15 +02:00
parent 8df6a4fff6
commit 1eed613b2a
21 changed files with 40 additions and 902 deletions

View File

@@ -39,13 +39,13 @@ kotlin {
//base
api("nl.astraeus:kotlin-css-generator:1.0.7")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
implementation("nl.astraeus:vst-ui-base:1.1.0-SNAPSHOT")
}
}
val jsMain by getting {
dependencies {
//base
implementation("nl.astraeus:kotlin-komponent-js:1.2.2")
implementation("nl.astraeus:vst-ui-base:1.0.0-SNAPSHOT")
}
}
val jsTest by getting {

View File

@@ -7,6 +7,8 @@ import kotlin.math.round
expect fun randomDouble(): Double
const val BUFFER_MULTIPLY = 4
@ExperimentalJsExport
@JsExport
class PhysicalString(
@@ -14,9 +16,9 @@ class PhysicalString(
var damping: Double,
) {
val sampleLength = 1.0 / sampleRate.toDouble()
val maxLength = sampleRate / Note.G9.freq
val maxLength = sampleRate / Note.NO01.freq
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 index = 0
var remaining = 0.0
@@ -26,7 +28,7 @@ class PhysicalString(
fun pluck(note: Note, velocity: Double, smoothing: Int = 0) {
available = false
currentNote = note
length = round(sampleRate / note.freq).toInt()
length = round(BUFFER_MULTIPLY * sampleRate / note.freq).toInt()
sample = 0
index = 0
@@ -37,7 +39,7 @@ class PhysicalString(
buffer[i] = -randomDouble() * 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 }
}
repeat(smoothing) {
@@ -58,18 +60,20 @@ class PhysicalString(
fun tick(): Double {
val result = buffer[index]
var newValue = 0.0
newValue += getValueFromBuffer(index + 1) * 0.2
newValue += getValueFromBuffer(index + 2) * 0.3
newValue += getValueFromBuffer(index + 3) * 0.3
newValue += getValueFromBuffer(index + 4) * 0.2
// newValue += getValueFromBuffer(index + 5) * 0.2
// newValue += getValueFromBuffer(index + 6) * 0.3
newValue *= damping
repeat(BUFFER_MULTIPLY) {
var newValue = 0.0
newValue += getValueFromBuffer(index + 1) * 0.1
newValue += getValueFromBuffer(index + 2) * 0.15
newValue += getValueFromBuffer(index + 3) * 0.25
newValue += getValueFromBuffer(index + 4) * 0.25
newValue += getValueFromBuffer(index + 5) * 0.15
newValue += getValueFromBuffer(index + 6) * 0.1
newValue *= damping
buffer[index] = newValue
buffer[index] = newValue
index = (index + 1) % length
index = (index + 1) % length
}
return result
}

View File

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

View File

@@ -3,18 +3,24 @@ package nl.astraeus.vst.string
import kotlinx.browser.document
import nl.astraeus.komp.Komponent
import nl.astraeus.komp.UnsafeMode
import nl.astraeus.vst.string.audio.VstStringWorklet
import nl.astraeus.vst.string.logger.log
import nl.astraeus.vst.string.midi.Midi
import nl.astraeus.vst.string.view.MainView
import nl.astraeus.vst.string.ws.WebsocketClient
import nl.astraeus.vst.ui.css.CssSettings
import nl.astraeus.vst.ui.view.BaseVstView
fun main() {
CssSettings.shortId = false
CssSettings.preFix = "vst-chip"
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()

View File

@@ -7,4 +7,4 @@ object AudioContextHandler {
}
}

View File

@@ -18,15 +18,10 @@ import nl.astraeus.css.properties.Display
import nl.astraeus.css.properties.FlexDirection
import nl.astraeus.css.properties.FontWeight
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.hsla
import nl.astraeus.css.properties.prc
import nl.astraeus.css.properties.px
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.cls
import nl.astraeus.komp.HtmlBuilder
@@ -48,7 +43,6 @@ import org.w3c.dom.HTMLSelectElement
object MainView : Komponent(), CssName {
private var messages: MutableList<String> = ArrayList()
var started = false
val playString = PhysicalStringView(
PhysicalString(
sampleRate = 48000,
@@ -70,22 +64,6 @@ object MainView : Komponent(), CssName {
override fun HtmlBuilder.render() {
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 {
+"VST Guitar"
}
@@ -175,9 +153,6 @@ object MainView : Komponent(), CssName {
object ButtonBarCss : CssName
object SelectedCss : CssName
object NoteBarCss : CssName
object StartSplashCss : CssName
object StartBoxCss : CssName
object StartButtonCss : CssName
object ControlsCss : CssName
private fun css() {
@@ -239,36 +214,6 @@ object MainView : Komponent(), CssName {
color(Css.currentStyle.mainFontColor)
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()) {
display(Display.flex)
flexDirection(FlexDirection.row)

View File

@@ -40,9 +40,7 @@ class PhysicalStringView(
}
private fun onAnimationFrame(time: Double) {
if (MainView.started) {
draw()
}
draw()
window.requestAnimationFrame(::onAnimationFrame)
}
@@ -89,7 +87,7 @@ class PhysicalStringView(
private fun draw() {
val ctx = context
if (ctx != null) {
if (ctx != null && ctx.canvas.isConnected) {
val width = ctx.canvas.width.toDouble()
val height = ctx.canvas.height.toDouble()
val halfHeight = height / 2.0

View File

@@ -18,7 +18,7 @@ object WaveformView : Komponent() {
}
fun onAnimationFrame(time: Double) {
if (MainView.started) {
if (VstStringWorklet.recording == null) {
VstStringWorklet.postMessage("start_recording")
}
@@ -50,6 +50,7 @@ object WaveformView : Komponent() {
ctx.stroke()
}
}
VstStringWorklet.recording = null
}
}
}

View File

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

View File

@@ -1,15 +1,10 @@
package nl.astraeus.vst.string
import com.zaxxer.hikari.HikariConfig
import io.undertow.Undertow
import io.undertow.UndertowOptions
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.base.Settings
import nl.astraeus.vst.base.db.Database
import nl.astraeus.vst.base.web.UndertowServer
import nl.astraeus.vst.string.logger.LogLevel
import nl.astraeus.vst.string.logger.Logger
import nl.astraeus.vst.string.web.RequestHandler
fun main() {
Logger.level = LogLevel.DEBUG
@@ -18,32 +13,13 @@ fun main() {
e.printStackTrace()
}
Class.forName("nl.astraeus.jdbc.Driver")
Settings.port = 9004
Settings.jdbcStatsPort = 6004
Database.initialize(HikariConfig().apply {
driverClassName = "nl.astraeus.jdbc.Driver"
jdbcUrl = "jdbc:stat:webServerPort=6002:jdbc:sqlite:data/chip.db"
username = "sa"
password = ""
maximumPoolSize = 25
isAutoCommit = false
Database.start()
validate()
})
val sessionHandler = SessionAttachmentHandler(
InMemorySessionManager("vst-session-manager"),
SessionCookieConfig()
UndertowServer.start(
"Vst String",
"/vst-string-worklet-ui.js"
)
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()
}

View File

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

View File

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

View File

@@ -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
*/
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +0,0 @@
package nl.astraeus.vst.string.web
class VstSession(
val patchId: String
)