generated from rnentjes/kotlin-server-web-empty
Refactor project structure and update dependencies
Removed old database-related implementations (`Database`, `Migrations`, and `Main`) and replaced them with a new focus on MIDI message handling. Updated project metadata, introduced multiplatform `MidiMessage` classes, and added publication/CI setup for Maven compatibility.
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
package nl.astraeus.midi.message
|
||||
|
||||
import nl.astraeus.tba.DataType
|
||||
import nl.astraeus.tba.MutableByteArrayHandler
|
||||
import nl.astraeus.tba.SlicedByteArray
|
||||
import nl.astraeus.tba.Type
|
||||
import nl.astraeus.tba.TypedByteArray
|
||||
import nl.astraeus.tba.blob
|
||||
import nl.astraeus.tba.double
|
||||
import nl.astraeus.tba.long
|
||||
|
||||
class MidiMessage() : TypedByteArray(
|
||||
Type("type", DataType.LONG),
|
||||
Type("generated", DataType.DOUBLE),
|
||||
Type("playTime", DataType.DOUBLE),
|
||||
Type("midi", DataType.BLOB, 240),
|
||||
) {
|
||||
var type by long("type")
|
||||
var generated by double("generated")
|
||||
var playTime by double("playTime")
|
||||
var midi by blob("data")
|
||||
|
||||
init {
|
||||
this.type = MidiMessageTypes.MIDI_DATA.typeId
|
||||
}
|
||||
|
||||
|
||||
constructor(data: ByteArray): this() {
|
||||
check(data.size == definition.size) {
|
||||
"Invalid data size: ${data.size} != ${definition.size}"
|
||||
}
|
||||
|
||||
this.data = MutableByteArrayHandler(data)
|
||||
}
|
||||
|
||||
constructor(playTime: Double, midi: ByteArray): this() {
|
||||
this.generated = getCurrentTime()
|
||||
this.playTime = playTime
|
||||
this.midi = SlicedByteArray(midi)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package nl.astraeus.midi.message
|
||||
|
||||
|
||||
inline fun <reified T> String.encodeToNumber(): T {
|
||||
val cls = T::class
|
||||
|
||||
val length = when(cls) {
|
||||
Byte::class -> 1
|
||||
Short::class -> 2
|
||||
Int::class -> 4
|
||||
Long::class -> 8
|
||||
else -> throw IllegalArgumentException("Unsupported type")
|
||||
}
|
||||
|
||||
var count = 0
|
||||
var result = 0L
|
||||
for (ch in this) {
|
||||
result = result shl 8
|
||||
result += ch.code and 0xff
|
||||
|
||||
if (count++ == length) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return when(cls) {
|
||||
Byte::class -> result.toByte() as T
|
||||
Short::class -> result.toShort() as T
|
||||
Int::class -> result.toInt() as T
|
||||
Long::class -> result as T
|
||||
else -> throw IllegalArgumentException("Unsupported type")
|
||||
}
|
||||
}
|
||||
|
||||
enum class MidiMessageTypes(
|
||||
val type: String,
|
||||
val typeId: Long,
|
||||
) {
|
||||
NONE("Unknown", 0L),
|
||||
MIDI_DATA("Timestamped midi data", "TimeMidi".encodeToNumber()),
|
||||
}
|
||||
3
src/commonMain/kotlin/nl/astraeus/midi/message/Time.kt
Normal file
3
src/commonMain/kotlin/nl/astraeus/midi/message/Time.kt
Normal file
@@ -0,0 +1,3 @@
|
||||
package nl.astraeus.midi.message
|
||||
|
||||
expect fun getCurrentTime(): Double
|
||||
3
src/jsMain/kotlin/nl/astraeus/midi/message/Time.js.kt
Normal file
3
src/jsMain/kotlin/nl/astraeus/midi/message/Time.js.kt
Normal file
@@ -0,0 +1,3 @@
|
||||
package nl.astraeus.midi.message
|
||||
|
||||
actual fun getCurrentTime(): Double = js("performance.now()").unsafeCast<Double>()
|
||||
5
src/jvmMain/kotlin/nl/astraeus/midi/message/Time.jvm.kt
Normal file
5
src/jvmMain/kotlin/nl/astraeus/midi/message/Time.jvm.kt
Normal file
@@ -0,0 +1,5 @@
|
||||
package nl.astraeus.midi.message
|
||||
|
||||
actual fun getCurrentTime(): Double {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package tmpl
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig
|
||||
import nl.astraeus.logger.Logger
|
||||
import tmpl.db.Database
|
||||
|
||||
val log = Logger()
|
||||
|
||||
fun main() {
|
||||
Thread.currentThread().uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { t, e ->
|
||||
log.warn(e) {
|
||||
e.message
|
||||
}
|
||||
}
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(
|
||||
object : Thread() {
|
||||
override fun run() {
|
||||
Database.vacuumDatabase()
|
||||
Database.closeDatabase()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Class.forName("nl.astraeus.jdbc.Driver")
|
||||
Database.initialize(HikariConfig().apply {
|
||||
driverClassName = "nl.astraeus.jdbc.Driver"
|
||||
jdbcUrl = "jdbc:stat:webServerPort=6001:jdbc:sqlite:data/midi-arrays.db"
|
||||
username = "sa"
|
||||
password = ""
|
||||
maximumPoolSize = 25
|
||||
isAutoCommit = false
|
||||
|
||||
validate()
|
||||
})
|
||||
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package tmpl.db
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
import java.sql.Connection
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlin.collections.set
|
||||
import kotlin.use
|
||||
|
||||
private val currentConnection = ThreadLocal<Connection>()
|
||||
|
||||
fun <T> transaction(
|
||||
block: (Connection) -> T
|
||||
): T {
|
||||
val hasConnection = currentConnection.get() != null
|
||||
var oldConnection: Connection? = null
|
||||
|
||||
if (!hasConnection) {
|
||||
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!")
|
||||
|
||||
fun vacuumDatabase() {
|
||||
getConnection().use {
|
||||
it.autoCommit = true
|
||||
|
||||
it.prepareStatement("VACUUM").use { ps ->
|
||||
ps.executeUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun closeDatabase() {
|
||||
ds?.close()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package tmpl.db
|
||||
|
||||
import tmpl.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("SELECT sqlite_version()"),
|
||||
)
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user