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:
2024-12-14 15:09:34 +01:00
parent 313f185447
commit c615d0e3b7
14 changed files with 201 additions and 254 deletions

1
.gitignore vendored
View File

@@ -44,3 +44,4 @@ bin/
### .kotlin ###
.kotlin
kotlin-js-store
gradle.properties

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

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

View File

@@ -1,64 +1,108 @@
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType
plugins {
kotlin("multiplatform") version "2.1.0"
kotlin("multiplatform") version "2.0.21"
`maven-publish`
signing
}
group = "nl.astraeus"
version = "1.0.0-SNAPSHOT"
version = "0.1.0"
repositories {
mavenCentral()
maven {
url = uri("https://gitea.astraeus.nl/api/packages/rnentjes/maven")
}
maven {
url = uri("https://gitea.astraeus.nl:8443/api/packages/rnentjes/maven")
}
}
kotlin {
jvmToolchain(17)
jvm()
js {
binaries.executable()
browser {
distribution {
outputDirectory.set(File("$projectDir/web/"))
}
}
browser()
}
sourceSets {
val commonMain by getting {
dependencies {
api("nl.astraeus:kotlin-simple-logging:1.1.1")
api("nl.astraeus:kotlin-css-generator:1.0.10")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0")
api("nl.astraeus:typed-byte-arrays:0.2.6")
}
}
val commonTest by getting
val jvmMain by getting {
dependencies {
implementation("io.undertow:undertow-core:2.3.14.Final")
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 jvmMain by getting
val jvmTest by getting
val jsMain 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>())
}

View File

@@ -1,5 +1,5 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0"
}
val REPO_NAME = "template"
rootProject.name = "midi-arrays"

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
package nl.astraeus.midi.message
expect fun getCurrentTime(): Double

View File

@@ -0,0 +1,3 @@
package nl.astraeus.midi.message
actual fun getCurrentTime(): Double = js("performance.now()").unsafeCast<Double>()

View File

@@ -0,0 +1,5 @@
package nl.astraeus.midi.message
actual fun getCurrentTime(): Double {
TODO("Not yet implemented")
}

View File

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

View File

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

View File

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