diff options
| author | Freya Murphy <freya@freyacat.org> | 2026-03-26 23:15:33 -0400 |
|---|---|---|
| committer | Freya Murphy <freya@freyacat.org> | 2026-03-27 23:09:23 -0400 |
| commit | f8322cd21cde68a72b05efbad3a05b8e67c0bdd0 (patch) | |
| tree | d7e60bc8fedadc8fa7ae725571cad1f398eaf6dc /core/src/db | |
| download | kenshinshideandseek2-f8322cd21cde68a72b05efbad3a05b8e67c0bdd0.tar.gz kenshinshideandseek2-f8322cd21cde68a72b05efbad3a05b8e67c0bdd0.tar.bz2 kenshinshideandseek2-f8322cd21cde68a72b05efbad3a05b8e67c0bdd0.zip | |
initial
Diffstat (limited to 'core/src/db')
| -rw-r--r-- | core/src/db/Database.kt | 80 | ||||
| -rw-r--r-- | core/src/db/Driver.kt | 79 | ||||
| -rw-r--r-- | core/src/db/Legacy.kt | 58 | ||||
| -rw-r--r-- | core/src/db/Player.kt | 62 |
4 files changed, 279 insertions, 0 deletions
diff --git a/core/src/db/Database.kt b/core/src/db/Database.kt new file mode 100644 index 0000000..27864b3 --- /dev/null +++ b/core/src/db/Database.kt @@ -0,0 +1,80 @@ +package cat.freya.khs.db + +import cat.freya.khs.Khs +import java.util.UUID +import org.jetbrains.exposed.v1.core.* +import org.jetbrains.exposed.v1.jdbc.* +import org.jetbrains.exposed.v1.jdbc.Database as Exposed +import org.jetbrains.exposed.v1.jdbc.transactions.transaction + +class Database(plugin: Khs) { + val driver = getDriver(plugin) + val source = driver.connect() + val db = Exposed.connect(source) + + init { + transaction(db) { SchemaUtils.create(Players) } + migrateLegacy() + } + + fun getPlayer(uuid: UUID): Player? = + transaction(db) { + val id = uuid.toString() + Players.selectAll().where { Players.uuid eq id }.map { it.toPlayer() }.singleOrNull() + } + + fun getPlayer(name: String): Player? = + transaction(db) { + Players.selectAll().where { Players.name eq name }.map { it.toPlayer() }.singleOrNull() + } + + fun getPlayers(page: UInt, pageSize: UInt): List<Player> = + transaction(db) { + val offset = page * pageSize + val wins = Players.hiderWins + Players.seekerWins + Players.selectAll() + .orderBy(wins to SortOrder.DESC) + .limit(pageSize.toInt()) + .offset(offset.toLong()) + .map { it.toPlayer() } + } + + fun getPlayerNames(limit: UInt, startsWith: String): List<String> = + transaction(db) { + Players.select(Players.name) + .where { Players.name like "$startsWith%" } + .orderBy(Players.name to SortOrder.ASC) + .limit(limit.toInt()) + .map { it[Players.name] } + .filterNotNull() + } + + fun upsertPlayer(player: Player) = transaction(db) { Players.upsert { it.fromPlayer(player) } } + + fun upsertName(u: UUID, n: String) = + transaction(db) { + Players.upsert { + it[uuid] = u.toString() + it[name] = n + } + } + + fun migrateLegacy() = + transaction(db) { + if (!LegacyPlayers.exists() || !LegacyNames.exists()) return@transaction + + val legacy = + LegacyPlayers.join( + LegacyNames, + JoinType.LEFT, + onColumn = LegacyPlayers.uuid, + otherColumn = LegacyNames.uuid, + ) + .selectAll() + .map { it.toLegacyPlayer() } + Players.insertIgnore { legacy.forEach { player -> it.fromPlayer(player) } } + + SchemaUtils.drop(LegacyPlayers) + SchemaUtils.drop(LegacyNames) + } +} diff --git a/core/src/db/Driver.kt b/core/src/db/Driver.kt new file mode 100644 index 0000000..c30e5c1 --- /dev/null +++ b/core/src/db/Driver.kt @@ -0,0 +1,79 @@ +package cat.freya.khs.db + +import cat.freya.khs.Khs +import cat.freya.khs.config.DatabaseConfig +import cat.freya.khs.config.DatabaseType +import com.zaxxer.hikari.HikariConfig +import com.zaxxer.hikari.HikariDataSource +import javax.sql.DataSource + +abstract class Driver { + abstract val driverClass: String + + abstract fun jdbcUrl(): String + + abstract fun configure(hikari: HikariConfig) + + fun connect(): DataSource { + // load driver for some reason + Class.forName(driverClass) + + val cores = Runtime.getRuntime().availableProcessors() + val hikari = + HikariConfig().apply { + jdbcUrl = jdbcUrl() + driverClassName = driverClass + maximumPoolSize = minOf(cores, 8) + configure(this) + } + return HikariDataSource(hikari) + } +} + +class SqliteDriver(val path: String) : Driver() { + override val driverClass = "org.sqlite.JDBC" + + override fun jdbcUrl(): String { + return "jdbc:sqlite:$path" + } + + override fun configure(hikari: HikariConfig) { + // sqlite is single threaded + hikari.maximumPoolSize = 1 + } +} + +class MysqlDriver(val config: DatabaseConfig) : Driver() { + override val driverClass = "com.mysql.cj.jdbc.Driver" + + override fun jdbcUrl(): String { + val port = config.port ?: 3006u + return "jdbc:mysql://${config.host}:${port}/${config.database}" + } + + override fun configure(hikari: HikariConfig) { + hikari.username = config.username + hikari.password = config.password + } +} + +class PostgresDriver(val config: DatabaseConfig) : Driver() { + override val driverClass = "org.postgresql.Driver" + + override fun jdbcUrl(): String { + val port = config.port ?: 5432u + return "jdbc:postgresql://${config.host}:${port}/${config.database}" + } + + override fun configure(hikari: HikariConfig) { + hikari.username = config.username + hikari.password = config.password + } +} + +fun getDriver(plugin: Khs): Driver = + when (plugin.config.database.type) { + DatabaseType.SQLITE -> SqliteDriver(plugin.shim.sqliteDatabasePath) + DatabaseType.MYSQL -> MysqlDriver(plugin.config.database) + DatabaseType.POSTGRES -> PostgresDriver(plugin.config.database) + } diff --git a/core/src/db/Legacy.kt b/core/src/db/Legacy.kt new file mode 100644 index 0000000..7f64ef2 --- /dev/null +++ b/core/src/db/Legacy.kt @@ -0,0 +1,58 @@ +package cat.freya.khs.db + +import java.nio.ByteBuffer +import java.util.UUID +import org.jetbrains.exposed.v1.core.ResultRow +import org.jetbrains.exposed.v1.core.Table + +// tables introduced in version 1.7.0 +// pre 1.7.x tables are NOT SUPPORTED + +object LegacyNames : Table("hs_names") { + val uuid = binary("uuid", 16) + val name = varchar("name", 48).nullable() + override val primaryKey = PrimaryKey(uuid, name) +} + +object LegacyPlayers : Table("hs_data") { + val uuid = binary("uuid", 16) + val hiderWins = integer("hider_wins").nullable() + val seekerWins = integer("seeker_wins").nullable() + val hiderGames = integer("hider_games").nullable() + val seekerGames = integer("seeker_games").nullable() + val hiderKills = integer("hider_kills").nullable() + val seekerKills = integer("seeker_kills").nullable() + val hiderDeaths = integer("hider_deaths").nullable() + val seekerDeaths = integer("seeker_deaths").nullable() +} + +fun ResultRow.toLegacyPlayer(): Player { + val uuidBuffer = ByteBuffer.wrap(this[LegacyPlayers.uuid]) + val uuidHigh = uuidBuffer.long + val uuidLow = uuidBuffer.long + val uuid = UUID(uuidHigh, uuidLow) + + val hiderGames = this[LegacyPlayers.hiderGames] ?: 0 + val seekerGames = this[LegacyPlayers.seekerGames] ?: 0 + val hiderWins = this[LegacyPlayers.hiderWins] ?: 0 + val seekerWins = this[LegacyPlayers.seekerWins] ?: 0 + val hiderLosses = hiderGames - hiderWins + val seekerLosses = seekerGames - seekerWins + val hiderKills = this[LegacyPlayers.hiderKills] ?: 0 + val seekerKills = this[LegacyPlayers.seekerKills] ?: 0 + val hiderDeaths = this[LegacyPlayers.hiderDeaths] ?: 0 + val seekerDeaths = this[LegacyPlayers.seekerDeaths] ?: 0 + + return Player( + uuid, + name = this[LegacyNames.name], + seekerWins = maxOf(seekerWins, 0).toUInt(), + hiderWins = maxOf(hiderWins, 0).toUInt(), + hiderLosses = maxOf(hiderLosses, 0).toUInt(), + seekerLosses = maxOf(seekerLosses, 0).toUInt(), + seekerKills = maxOf(seekerKills, 0).toUInt(), + hiderKills = maxOf(hiderKills, 0).toUInt(), + seekerDeaths = maxOf(seekerDeaths, 0).toUInt(), + hiderDeaths = maxOf(hiderDeaths, 0).toUInt(), + ) +} diff --git a/core/src/db/Player.kt b/core/src/db/Player.kt new file mode 100644 index 0000000..ccebcaa --- /dev/null +++ b/core/src/db/Player.kt @@ -0,0 +1,62 @@ +package cat.freya.khs.db + +import java.util.UUID +import org.jetbrains.exposed.v1.core.ResultRow +import org.jetbrains.exposed.v1.core.Table +import org.jetbrains.exposed.v1.core.statements.UpdateBuilder + +object Players : Table("hs_players") { + val uuid = varchar("uuid", 36) + val name = text("name").nullable() + val seekerWins = integer("seeker_wins").default(0) + val hiderWins = integer("hider_wins").default(0) + val seekerLosses = integer("seeker_losses").default(0) + val hiderLosses = integer("hider_losses").default(0) + val seekerKills = integer("seeker_kills").default(0) + val hiderKills = integer("hider_kills").default(0) + val seekerDeaths = integer("seeker_deaths").default(0) + val hiderDeaths = integer("hider_deaths").default(0) + + override val primaryKey = PrimaryKey(uuid) +} + +data class Player( + val uuid: UUID, + var name: String? = null, + var seekerWins: UInt = 0u, + var hiderWins: UInt = 0u, + var seekerLosses: UInt = 0u, + var hiderLosses: UInt = 0u, + var seekerKills: UInt = 0u, + var hiderKills: UInt = 0u, + var seekerDeaths: UInt = 0u, + var hiderDeaths: UInt = 0u, +) + +fun ResultRow.toPlayer(): Player { + return Player( + uuid = UUID.fromString(this[Players.uuid]), + name = this[Players.name], + seekerWins = this[Players.seekerWins].toUInt(), + hiderWins = this[Players.hiderWins].toUInt(), + seekerLosses = this[Players.seekerLosses].toUInt(), + hiderLosses = this[Players.hiderLosses].toUInt(), + seekerKills = this[Players.seekerKills].toUInt(), + hiderKills = this[Players.hiderKills].toUInt(), + seekerDeaths = this[Players.seekerDeaths].toUInt(), + hiderDeaths = this[Players.hiderDeaths].toUInt(), + ) +} + +fun UpdateBuilder<*>.fromPlayer(player: Player) { + this[Players.uuid] = player.uuid.toString() + this[Players.name] = player.name + this[Players.seekerWins] = player.seekerWins.toInt() + this[Players.hiderWins] = player.hiderWins.toInt() + this[Players.seekerLosses] = player.seekerLosses.toInt() + this[Players.hiderLosses] = player.hiderLosses.toInt() + this[Players.seekerKills] = player.seekerKills.toInt() + this[Players.hiderKills] = player.hiderKills.toInt() + this[Players.seekerDeaths] = player.seekerDeaths.toInt() + this[Players.hiderDeaths] = player.hiderDeaths.toInt() +} |