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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -44,3 +44,4 @@ bin/
|
|||||||
### .kotlin ###
|
### .kotlin ###
|
||||||
.kotlin
|
.kotlin
|
||||||
kotlin-js-store
|
kotlin-js-store
|
||||||
|
gradle.properties
|
||||||
|
|||||||
8
.idea/artifacts/midi_arrays_js_1_0_0_SNAPSHOT.xml
generated
Normal file
8
.idea/artifacts/midi_arrays_js_1_0_0_SNAPSHOT.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="midi-arrays-js-1.0.0-SNAPSHOT">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="midi-arrays-js-1.0.0-SNAPSHOT.jar">
|
||||||
|
<element id="module-output" name="midi-arrays.jsMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
8
.idea/artifacts/midi_arrays_jvm_1_0_0_SNAPSHOT.xml
generated
Normal file
8
.idea/artifacts/midi_arrays_jvm_1_0_0_SNAPSHOT.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="midi-arrays-jvm-1.0.0-SNAPSHOT">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="midi-arrays-jvm-1.0.0-SNAPSHOT.jar">
|
||||||
|
<element id="module-output" name="midi-arrays.jvmMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
7
.idea/jsLibraryMappings.xml
generated
Normal file
7
.idea/jsLibraryMappings.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JavaScriptLibraryMappings">
|
||||||
|
<excludedPredefinedLibrary name="midi-arrays/build/js/node_modules" />
|
||||||
|
<excludedPredefinedLibrary name="midi-arrays/build/js/packages/midi-arrays-test/node_modules" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
120
build.gradle.kts
120
build.gradle.kts
@@ -1,64 +1,108 @@
|
|||||||
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType
|
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("multiplatform") version "2.1.0"
|
kotlin("multiplatform") version "2.0.21"
|
||||||
|
`maven-publish`
|
||||||
|
signing
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "nl.astraeus"
|
group = "nl.astraeus"
|
||||||
version = "1.0.0-SNAPSHOT"
|
version = "0.1.0"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven {
|
|
||||||
url = uri("https://gitea.astraeus.nl/api/packages/rnentjes/maven")
|
|
||||||
}
|
|
||||||
maven {
|
maven {
|
||||||
url = uri("https://gitea.astraeus.nl:8443/api/packages/rnentjes/maven")
|
url = uri("https://gitea.astraeus.nl:8443/api/packages/rnentjes/maven")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvmToolchain(17)
|
|
||||||
jvm()
|
jvm()
|
||||||
js {
|
js {
|
||||||
binaries.executable()
|
browser()
|
||||||
browser {
|
|
||||||
distribution {
|
|
||||||
outputDirectory.set(File("$projectDir/web/"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sourceSets {
|
sourceSets {
|
||||||
val commonMain by getting {
|
val commonMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
api("nl.astraeus:kotlin-simple-logging:1.1.1")
|
api("nl.astraeus:typed-byte-arrays:0.2.6")
|
||||||
api("nl.astraeus:kotlin-css-generator:1.0.10")
|
|
||||||
|
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val commonTest by getting
|
val commonTest by getting
|
||||||
val jvmMain by getting {
|
val jvmMain by getting
|
||||||
dependencies {
|
val jvmTest by getting
|
||||||
implementation("io.undertow:undertow-core:2.3.14.Final")
|
val jsMain by getting
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.11.0")
|
|
||||||
|
|
||||||
implementation("org.xerial:sqlite-jdbc:3.32.3.2")
|
|
||||||
implementation("com.zaxxer:HikariCP:4.0.3")
|
|
||||||
implementation("nl.astraeus:simple-jdbc-stats:1.6.1") {
|
|
||||||
exclude(group = "org.slf4j", module = "slf4j-api")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val jvmTest by getting {
|
|
||||||
dependencies {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val jsMain by getting {
|
|
||||||
dependencies {
|
|
||||||
implementation("nl.astraeus:kotlin-komponent:1.2.4")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val jsTest by getting
|
val jsTest by getting
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extra["PUBLISH_GROUP_ID"] = group
|
||||||
|
extra["PUBLISH_VERSION"] = version
|
||||||
|
extra["PUBLISH_ARTIFACT_ID"] = name
|
||||||
|
|
||||||
|
// Stub secrets to let the project sync and build without the publication values set up
|
||||||
|
val signingKeyId: String? by project
|
||||||
|
val signingPassword: String? by project
|
||||||
|
val signingSecretKeyRingFile: String? by project
|
||||||
|
|
||||||
|
extra["signing.keyId"] = signingKeyId
|
||||||
|
extra["signing.password"] = signingPassword
|
||||||
|
extra["signing.secretKeyRingFile"] = signingSecretKeyRingFile
|
||||||
|
|
||||||
|
val javadocJar by tasks.registering(Jar::class) {
|
||||||
|
archiveClassifier.set("javadoc")
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
name = "gitea"
|
||||||
|
setUrl("https://gitea.astraeus.nl:8443/api/packages/rnentjes/maven")
|
||||||
|
|
||||||
|
credentials {
|
||||||
|
val giteaUsername: String? by project
|
||||||
|
val giteaPassword: String? by project
|
||||||
|
|
||||||
|
username = giteaUsername
|
||||||
|
password = giteaPassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure all publications
|
||||||
|
publications.withType<MavenPublication> {
|
||||||
|
// Stub javadoc.jar artifact
|
||||||
|
artifact(javadocJar.get())
|
||||||
|
|
||||||
|
// Provide artifacts information requited by Maven Central
|
||||||
|
pom {
|
||||||
|
name.set("typed-byte-arrays")
|
||||||
|
description.set("Typed byte arrays")
|
||||||
|
url.set("https://gitea.astraeus.nl/rnentjes/typed-byte-arrays")
|
||||||
|
|
||||||
|
licenses {
|
||||||
|
license {
|
||||||
|
name.set("MIT")
|
||||||
|
url.set("https://opensource.org/licenses/MIT")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id.set("rnentjes")
|
||||||
|
name.set("Rien Nentjes")
|
||||||
|
email.set("info@nentjes.com")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scm {
|
||||||
|
url.set("https://gitea.astraeus.nl/rnentjes/typed-byte-arrays")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signing {
|
||||||
|
sign(publishing.publications)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<PublishToMavenRepository> {
|
||||||
|
dependsOn(tasks.withType<Sign>())
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0"
|
id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0"
|
||||||
}
|
}
|
||||||
val REPO_NAME = "template"
|
|
||||||
rootProject.name = "midi-arrays"
|
rootProject.name = "midi-arrays"
|
||||||
|
|||||||
@@ -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