diff options
Diffstat (limited to 'src/main/kotlin/io/dico/parcels2/storage')
11 files changed, 805 insertions, 803 deletions
diff --git a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt index 12d5bc7..63a5db6 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt @@ -1,69 +1,69 @@ -package io.dico.parcels2.storage - -import io.dico.parcels2.* -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.Job -import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.channels.SendChannel -import org.joda.time.DateTime -import java.util.UUID -import kotlin.coroutines.CoroutineContext - -interface Backing { - - val name: String - - val isConnected: Boolean - - val coroutineContext: CoroutineContext - - fun launchJob(job: Backing.() -> Unit): Job - - fun <T> launchFuture(future: Backing.() -> T): Deferred<T> - - fun <T> openChannel(future: Backing.(SendChannel<T>) -> Unit): ReceiveChannel<T> - - fun <T> openChannelForWriting(future: Backing.(T) -> Unit): SendChannel<T> - - - fun init() - - fun shutdown() - - - fun getWorldCreationTime(worldId: ParcelWorldId): DateTime? - - fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime) - - fun getPlayerUuidForName(name: String): UUID? - - fun updatePlayerName(uuid: UUID, name: String) - - fun transmitParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>) - - fun transmitAllParcelData(channel: SendChannel<DataPair>) - - fun readParcelData(parcel: ParcelId): ParcelDataHolder? - - fun getOwnedParcels(user: PlayerProfile): List<ParcelId> - - fun getNumParcels(user: PlayerProfile): Int = getOwnedParcels(user).size - - - fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?) - - fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) - - fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean) - - fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege) - - fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration) - - - fun transmitAllGlobalPrivileges(channel: SendChannel<PrivilegePair<PlayerProfile>>) - - fun readGlobalPrivileges(owner: PlayerProfile): PrivilegesHolder? - - fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege) -} +package io.dico.parcels2.storage
+
+import io.dico.parcels2.*
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.ReceiveChannel
+import kotlinx.coroutines.channels.SendChannel
+import org.joda.time.DateTime
+import java.util.UUID
+import kotlin.coroutines.CoroutineContext
+
+interface Backing {
+
+ val name: String
+
+ val isConnected: Boolean
+
+ val coroutineContext: CoroutineContext
+
+ fun launchJob(job: Backing.() -> Unit): Job
+
+ fun <T> launchFuture(future: Backing.() -> T): Deferred<T>
+
+ fun <T> openChannel(future: Backing.(SendChannel<T>) -> Unit): ReceiveChannel<T>
+
+ fun <T> openChannelForWriting(future: Backing.(T) -> Unit): SendChannel<T>
+
+
+ fun init()
+
+ fun shutdown()
+
+
+ fun getWorldCreationTime(worldId: ParcelWorldId): DateTime?
+
+ fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime)
+
+ fun getPlayerUuidForName(name: String): UUID?
+
+ fun updatePlayerName(uuid: UUID, name: String)
+
+ fun transmitParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>)
+
+ fun transmitAllParcelData(channel: SendChannel<DataPair>)
+
+ fun readParcelData(parcel: ParcelId): ParcelDataHolder?
+
+ fun getOwnedParcels(user: PlayerProfile): List<ParcelId>
+
+ fun getNumParcels(user: PlayerProfile): Int = getOwnedParcels(user).size
+
+
+ fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?)
+
+ fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?)
+
+ fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean)
+
+ fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege)
+
+ fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration)
+
+
+ fun transmitAllGlobalPrivileges(channel: SendChannel<PrivilegePair<PlayerProfile>>)
+
+ fun readGlobalPrivileges(owner: PlayerProfile): PrivilegesHolder?
+
+ fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege)
+}
diff --git a/src/main/kotlin/io/dico/parcels2/storage/DataConverters.kt b/src/main/kotlin/io/dico/parcels2/storage/DataConverters.kt index 80f41b2..54f7677 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/DataConverters.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/DataConverters.kt @@ -1,38 +1,38 @@ -package io.dico.parcels2.storage - -import java.lang.IllegalArgumentException -import java.nio.ByteBuffer -import java.util.UUID - -/* For putting it into the database */ -fun UUID.toByteArray(): ByteArray = - ByteBuffer.allocate(16).apply { - putLong(mostSignificantBits) - putLong(leastSignificantBits) - }.array() - -/* For getting it out of the database */ -fun ByteArray.toUUID(): UUID = - ByteBuffer.wrap(this).run { - val mostSignificantBits = getLong() - val leastSignificantBits = getLong() - UUID(mostSignificantBits, leastSignificantBits) - } - -/* For putting it into the database */ -fun IntArray.toByteArray(): ByteArray = - ByteBuffer.allocate(size * Int.SIZE_BYTES).also { buf -> - buf.asIntBuffer().put(this) - }.array() - -/* For getting it out of the database */ -fun ByteArray.toIntArray(): IntArray { - if (this.size % Int.SIZE_BYTES != 0) - throw IllegalArgumentException("Size must be divisible by ${Int.SIZE_BYTES}") - - return ByteBuffer.wrap(this).run { - IntArray(remaining() / 4).also { array -> - asIntBuffer().get(array) - } - } +package io.dico.parcels2.storage
+
+import java.lang.IllegalArgumentException
+import java.nio.ByteBuffer
+import java.util.UUID
+
+/* For putting it into the database */
+fun UUID.toByteArray(): ByteArray =
+ ByteBuffer.allocate(16).apply {
+ putLong(mostSignificantBits)
+ putLong(leastSignificantBits)
+ }.array()
+
+/* For getting it out of the database */
+fun ByteArray.toUUID(): UUID =
+ ByteBuffer.wrap(this).run {
+ val mostSignificantBits = getLong()
+ val leastSignificantBits = getLong()
+ UUID(mostSignificantBits, leastSignificantBits)
+ }
+
+/* For putting it into the database */
+fun IntArray.toByteArray(): ByteArray =
+ ByteBuffer.allocate(size * Int.SIZE_BYTES).also { buf ->
+ buf.asIntBuffer().put(this)
+ }.array()
+
+/* For getting it out of the database */
+fun ByteArray.toIntArray(): IntArray {
+ if (this.size % Int.SIZE_BYTES != 0)
+ throw IllegalArgumentException("Size must be divisible by ${Int.SIZE_BYTES}")
+
+ return ByteBuffer.wrap(this).run {
+ IntArray(remaining() / 4).also { array ->
+ asIntBuffer().get(array)
+ }
+ }
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/storage/Hikari.kt b/src/main/kotlin/io/dico/parcels2/storage/Hikari.kt index 480d533..f3030f6 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Hikari.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Hikari.kt @@ -1,73 +1,73 @@ -package io.dico.parcels2.storage - -import com.zaxxer.hikari.HikariConfig -import io.dico.parcels2.options.DataConnectionOptions - -fun getHikariConfig(dialectName: String, - dco: DataConnectionOptions): HikariConfig = HikariConfig().apply { - - val (address, port) = dco.splitAddressAndPort() ?: throw IllegalArgumentException("Invalid address: ${dco.address}") - - when (dialectName) { - "postgresql" -> run { - dataSourceClassName = "org.postgresql.ds.PGSimpleDataSource" - dataSourceProperties["serverName"] = address - dataSourceProperties["portNumber"] = port.toString() - dataSourceProperties["databaseName"] = dco.database - } - - "mariadb" -> run { - dataSourceClassName = "org.mariadb.jdbc.MariaDbDataSource" - dataSourceProperties["serverName"] = address - dataSourceProperties["port"] = port.toString() - dataSourceProperties["databaseName"] = dco.database - dataSourceProperties["properties"] = "useUnicode=true;characterEncoding=utf8" - } - - else -> throw IllegalArgumentException("Unsupported dialect: $dialectName") - } - - poolName = "parcels" - maximumPoolSize = dco.poolSize - username = dco.username - password = dco.password - connectionTimeout = 15000 - leakDetectionThreshold = 10000 - connectionTestQuery = "SELECT 1" - - - /* - - addDataSourceProperty("serverName", address) - addDataSourceProperty("port", port.toString()) - addDataSourceProperty("databaseName", dco.database) - - // copied from github.com/lucko/LuckPerms - if (dialectName.toLowerCase() == "mariadb") { - addDataSourceProperty("properties", "useUnicode=true;characterEncoding=utf8") - } else if (dialectName.toLowerCase() == "h2") { - dataSourceProperties.remove("serverName") - dataSourceProperties.remove("port") - dataSourceProperties.remove("databaseName") - addDataSourceProperty("url", "jdbc:h2:${if (address.isBlank()) "" else "tcp://$address/"}~/${dco.database}") - } else if (dialectName.toLowerCase() == "mysql") { - // doesn't exist on the MariaDB driver - addDataSourceProperty("cachePrepStmts", "true") - addDataSourceProperty("alwaysSendSetIsolation", "false") - addDataSourceProperty("cacheServerConfiguration", "true") - addDataSourceProperty("elideSetAutoCommits", "true") - addDataSourceProperty("useLocalSessionState", "true") - - // already set as default on mariadb - addDataSourceProperty("useServerPrepStmts", "true") - addDataSourceProperty("prepStmtCacheSize", "250") - addDataSourceProperty("prepStmtCacheSqlLimit", "2048") - addDataSourceProperty("cacheCallableStmts", "true") - - // make sure unicode characters can be used. - addDataSourceProperty("characterEncoding", "utf8") - addDataSourceProperty("useUnicode", "true") - } else { - - }*/ -} +package io.dico.parcels2.storage
+
+import com.zaxxer.hikari.HikariConfig
+import io.dico.parcels2.options.DataConnectionOptions
+
+fun getHikariConfig(dialectName: String,
+ dco: DataConnectionOptions): HikariConfig = HikariConfig().apply {
+
+ val (address, port) = dco.splitAddressAndPort() ?: throw IllegalArgumentException("Invalid address: ${dco.address}")
+
+ when (dialectName) {
+ "postgresql" -> run {
+ dataSourceClassName = "org.postgresql.ds.PGSimpleDataSource"
+ dataSourceProperties["serverName"] = address
+ dataSourceProperties["portNumber"] = port.toString()
+ dataSourceProperties["databaseName"] = dco.database
+ }
+
+ "mariadb" -> run {
+ dataSourceClassName = "org.mariadb.jdbc.MariaDbDataSource"
+ dataSourceProperties["serverName"] = address
+ dataSourceProperties["port"] = port.toString()
+ dataSourceProperties["databaseName"] = dco.database
+ dataSourceProperties["properties"] = "useUnicode=true;characterEncoding=utf8"
+ }
+
+ else -> throw IllegalArgumentException("Unsupported dialect: $dialectName")
+ }
+
+ poolName = "parcels"
+ maximumPoolSize = dco.poolSize
+ username = dco.username
+ password = dco.password
+ connectionTimeout = 15000
+ leakDetectionThreshold = 10000
+ connectionTestQuery = "SELECT 1"
+
+
+ /*
+
+ addDataSourceProperty("serverName", address)
+ addDataSourceProperty("port", port.toString())
+ addDataSourceProperty("databaseName", dco.database)
+
+ // copied from github.com/lucko/LuckPerms
+ if (dialectName.toLowerCase() == "mariadb") {
+ addDataSourceProperty("properties", "useUnicode=true;characterEncoding=utf8")
+ } else if (dialectName.toLowerCase() == "h2") {
+ dataSourceProperties.remove("serverName")
+ dataSourceProperties.remove("port")
+ dataSourceProperties.remove("databaseName")
+ addDataSourceProperty("url", "jdbc:h2:${if (address.isBlank()) "" else "tcp://$address/"}~/${dco.database}")
+ } else if (dialectName.toLowerCase() == "mysql") {
+ // doesn't exist on the MariaDB driver
+ addDataSourceProperty("cachePrepStmts", "true")
+ addDataSourceProperty("alwaysSendSetIsolation", "false")
+ addDataSourceProperty("cacheServerConfiguration", "true")
+ addDataSourceProperty("elideSetAutoCommits", "true")
+ addDataSourceProperty("useLocalSessionState", "true")
+
+ // already set as default on mariadb
+ addDataSourceProperty("useServerPrepStmts", "true")
+ addDataSourceProperty("prepStmtCacheSize", "250")
+ addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
+ addDataSourceProperty("cacheCallableStmts", "true")
+
+ // make sure unicode characters can be used.
+ addDataSourceProperty("characterEncoding", "utf8")
+ addDataSourceProperty("useUnicode", "true")
+ } else {
+
+ }*/
+}
diff --git a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt index d718f20..f76aad6 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt @@ -1,114 +1,114 @@ -@file:Suppress("NOTHING_TO_INLINE") - -package io.dico.parcels2.storage - -import io.dico.parcels2.* -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.Job -import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.channels.SendChannel -import kotlinx.coroutines.launch -import org.joda.time.DateTime -import java.util.UUID -import kotlin.coroutines.CoroutineContext - -typealias DataPair = Pair<ParcelId, ParcelDataHolder?> -typealias PrivilegePair<TAttach> = Pair<TAttach, PrivilegesHolder> - -interface Storage { - val name: String - val isConnected: Boolean - - fun init(): Job - - fun shutdown(): Job - - - fun getWorldCreationTime(worldId: ParcelWorldId): Deferred<DateTime?> - - fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime): Job - - fun getPlayerUuidForName(name: String): Deferred<UUID?> - - fun updatePlayerName(uuid: UUID, name: String): Job - - fun readParcelData(parcel: ParcelId): Deferred<ParcelDataHolder?> - - fun transmitParcelData(parcels: Sequence<ParcelId>): ReceiveChannel<DataPair> - - fun transmitAllParcelData(): ReceiveChannel<DataPair> - - fun getOwnedParcels(user: PlayerProfile): Deferred<List<ParcelId>> - - fun getNumParcels(user: PlayerProfile): Deferred<Int> - - - fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?): Job - - fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?): Job - - fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean): Job - - fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege): Job - - fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration): Job - - - fun transmitAllGlobalPrivileges(): ReceiveChannel<PrivilegePair<PlayerProfile>> - - fun readGlobalPrivileges(owner: PlayerProfile): Deferred<PrivilegesHolder?> - - fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege): Job - - - fun getChannelToUpdateParcelData(): SendChannel<Pair<ParcelId, ParcelDataHolder>> -} - -class BackedStorage internal constructor(val b: Backing) : Storage, CoroutineScope { - override val name get() = b.name - override val isConnected get() = b.isConnected - override val coroutineContext: CoroutineContext get() = b.coroutineContext - - override fun init() = launch { b.init() } - - override fun shutdown() = launch { b.shutdown() } - - - override fun getWorldCreationTime(worldId: ParcelWorldId): Deferred<DateTime?> = b.launchFuture { b.getWorldCreationTime(worldId) } - - override fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime): Job = b.launchJob { b.setWorldCreationTime(worldId, time) } - - override fun getPlayerUuidForName(name: String): Deferred<UUID?> = b.launchFuture { b.getPlayerUuidForName(name) } - - override fun updatePlayerName(uuid: UUID, name: String): Job = b.launchJob { b.updatePlayerName(uuid, name) } - - override fun readParcelData(parcel: ParcelId) = b.launchFuture { b.readParcelData(parcel) } - - override fun transmitParcelData(parcels: Sequence<ParcelId>) = b.openChannel<DataPair> { b.transmitParcelData(it, parcels) } - - override fun transmitAllParcelData() = b.openChannel<DataPair> { b.transmitAllParcelData(it) } - - override fun getOwnedParcels(user: PlayerProfile) = b.launchFuture { b.getOwnedParcels(user) } - - override fun getNumParcels(user: PlayerProfile) = b.launchFuture { b.getNumParcels(user) } - - override fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?) = b.launchJob { b.setParcelData(parcel, data) } - - override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) = b.launchJob { b.setParcelOwner(parcel, owner) } - - override fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean): Job = b.launchJob { b.setParcelOwnerSignOutdated(parcel, outdated) } - - override fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege) = b.launchJob { b.setLocalPrivilege(parcel, player, privilege) } - - override fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration) = b.launchJob { b.setParcelOptionsInteractConfig(parcel, config) } - - - override fun transmitAllGlobalPrivileges(): ReceiveChannel<PrivilegePair<PlayerProfile>> = b.openChannel { b.transmitAllGlobalPrivileges(it) } - - override fun readGlobalPrivileges(owner: PlayerProfile): Deferred<PrivilegesHolder?> = b.launchFuture { b.readGlobalPrivileges(owner) } - - override fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege) = b.launchJob { b.setGlobalPrivilege(owner, player, privilege) } - - override fun getChannelToUpdateParcelData(): SendChannel<Pair<ParcelId, ParcelDataHolder>> = b.openChannelForWriting { b.setParcelData(it.first, it.second) } -} +@file:Suppress("NOTHING_TO_INLINE")
+
+package io.dico.parcels2.storage
+
+import io.dico.parcels2.*
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.ReceiveChannel
+import kotlinx.coroutines.channels.SendChannel
+import kotlinx.coroutines.launch
+import org.joda.time.DateTime
+import java.util.UUID
+import kotlin.coroutines.CoroutineContext
+
+typealias DataPair = Pair<ParcelId, ParcelDataHolder?>
+typealias PrivilegePair<TAttach> = Pair<TAttach, PrivilegesHolder>
+
+interface Storage {
+ val name: String
+ val isConnected: Boolean
+
+ fun init(): Job
+
+ fun shutdown(): Job
+
+
+ fun getWorldCreationTime(worldId: ParcelWorldId): Deferred<DateTime?>
+
+ fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime): Job
+
+ fun getPlayerUuidForName(name: String): Deferred<UUID?>
+
+ fun updatePlayerName(uuid: UUID, name: String): Job
+
+ fun readParcelData(parcel: ParcelId): Deferred<ParcelDataHolder?>
+
+ fun transmitParcelData(parcels: Sequence<ParcelId>): ReceiveChannel<DataPair>
+
+ fun transmitAllParcelData(): ReceiveChannel<DataPair>
+
+ fun getOwnedParcels(user: PlayerProfile): Deferred<List<ParcelId>>
+
+ fun getNumParcels(user: PlayerProfile): Deferred<Int>
+
+
+ fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?): Job
+
+ fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?): Job
+
+ fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean): Job
+
+ fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege): Job
+
+ fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration): Job
+
+
+ fun transmitAllGlobalPrivileges(): ReceiveChannel<PrivilegePair<PlayerProfile>>
+
+ fun readGlobalPrivileges(owner: PlayerProfile): Deferred<PrivilegesHolder?>
+
+ fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege): Job
+
+
+ fun getChannelToUpdateParcelData(): SendChannel<Pair<ParcelId, ParcelDataHolder>>
+}
+
+class BackedStorage internal constructor(val b: Backing) : Storage, CoroutineScope {
+ override val name get() = b.name
+ override val isConnected get() = b.isConnected
+ override val coroutineContext: CoroutineContext get() = b.coroutineContext
+
+ override fun init() = launch { b.init() }
+
+ override fun shutdown() = launch { b.shutdown() }
+
+
+ override fun getWorldCreationTime(worldId: ParcelWorldId): Deferred<DateTime?> = b.launchFuture { b.getWorldCreationTime(worldId) }
+
+ override fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime): Job = b.launchJob { b.setWorldCreationTime(worldId, time) }
+
+ override fun getPlayerUuidForName(name: String): Deferred<UUID?> = b.launchFuture { b.getPlayerUuidForName(name) }
+
+ override fun updatePlayerName(uuid: UUID, name: String): Job = b.launchJob { b.updatePlayerName(uuid, name) }
+
+ override fun readParcelData(parcel: ParcelId) = b.launchFuture { b.readParcelData(parcel) }
+
+ override fun transmitParcelData(parcels: Sequence<ParcelId>) = b.openChannel<DataPair> { b.transmitParcelData(it, parcels) }
+
+ override fun transmitAllParcelData() = b.openChannel<DataPair> { b.transmitAllParcelData(it) }
+
+ override fun getOwnedParcels(user: PlayerProfile) = b.launchFuture { b.getOwnedParcels(user) }
+
+ override fun getNumParcels(user: PlayerProfile) = b.launchFuture { b.getNumParcels(user) }
+
+ override fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?) = b.launchJob { b.setParcelData(parcel, data) }
+
+ override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) = b.launchJob { b.setParcelOwner(parcel, owner) }
+
+ override fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean): Job = b.launchJob { b.setParcelOwnerSignOutdated(parcel, outdated) }
+
+ override fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege) = b.launchJob { b.setLocalPrivilege(parcel, player, privilege) }
+
+ override fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration) = b.launchJob { b.setParcelOptionsInteractConfig(parcel, config) }
+
+
+ override fun transmitAllGlobalPrivileges(): ReceiveChannel<PrivilegePair<PlayerProfile>> = b.openChannel { b.transmitAllGlobalPrivileges(it) }
+
+ override fun readGlobalPrivileges(owner: PlayerProfile): Deferred<PrivilegesHolder?> = b.launchFuture { b.readGlobalPrivileges(owner) }
+
+ override fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege) = b.launchJob { b.setGlobalPrivilege(owner, player, privilege) }
+
+ override fun getChannelToUpdateParcelData(): SendChannel<Pair<ParcelId, ParcelDataHolder>> = b.openChannelForWriting { b.setParcelData(it.first, it.second) }
+}
diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt index e49be79..d9e3071 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt @@ -72,9 +72,11 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi override fun init() { synchronized { if (isShutdown || isConnected) throw IllegalStateException() - dataSource = dataSourceFactory() - database = Database.connect(dataSource!!) - transaction(database!!) { + val dataSource = dataSourceFactory() + this.dataSource = dataSource + val database = Database.connect(dataSource) + this.database = database + transaction(database) { create(WorldsT, ProfilesT, ParcelsT, ParcelOptionsT, PrivilegesLocalT, PrivilegesGlobalT) } } @@ -84,7 +86,7 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi synchronized { if (isShutdown) throw IllegalStateException() isShutdown = true - coroutineContext[Job]!!.cancel(CancellationException("ExposedBacking shutdown")) + coroutineContext.cancel(CancellationException("ExposedBacking shutdown")) dataSource?.let { (it as? HikariDataSource)?.close() } diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt index 0245625..7640c0c 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt @@ -1,74 +1,74 @@ -package io.dico.parcels2.storage.exposed - -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.Function -import org.jetbrains.exposed.sql.statements.InsertStatement -import org.jetbrains.exposed.sql.transactions.TransactionManager - -class UpsertStatement<Key : Any>(table: Table, conflictColumn: Column<*>? = null, conflictIndex: Index? = null) : InsertStatement<Key>(table, false) { - val indexName: String - val indexColumns: List<Column<*>> - - init { - when { - conflictIndex != null -> { - indexName = conflictIndex.indexName - indexColumns = conflictIndex.columns - } - conflictColumn != null -> { - indexName = conflictColumn.name - indexColumns = listOf(conflictColumn) - } - else -> throw IllegalArgumentException() - } - } - - override fun prepareSQL(transaction: Transaction) = buildString { - append(super.prepareSQL(transaction)) - - val dialect = transaction.db.vendor - if (dialect == "postgresql") { - - append(" ON CONFLICT(") - append(indexName) - append(") DO UPDATE SET ") - - values.keys.filter { it !in indexColumns }.joinTo(this) { "${transaction.identity(it)}=EXCLUDED.${transaction.identity(it)}" } - - } else { - - append(" ON DUPLICATE KEY UPDATE ") - values.keys.filter { it !in indexColumns }.joinTo(this) { "${transaction.identity(it)}=VALUES(${transaction.identity(it)})" } - - } - } - -} - -inline fun <T : Table> T.upsert(conflictColumn: Column<*>? = null, conflictIndex: Index? = null, body: T.(UpsertStatement<Number>) -> Unit) = - UpsertStatement<Number>(this, conflictColumn, conflictIndex).apply { - body(this) - execute(TransactionManager.current()) - } - -fun Table.indexR(customIndexName: String? = null, isUnique: Boolean = false, vararg columns: Column<*>): Index { - val index = Index(columns.toList(), isUnique, customIndexName) - indices.add(index) - return index -} - -fun Table.uniqueIndexR(customIndexName: String? = null, vararg columns: Column<*>): Index = indexR(customIndexName, true, *columns) - -fun <T : Int?> ExpressionWithColumnType<T>.abs(): Function<T> = Abs(this) - -class Abs<T : Int?>(val expr: Expression<T>) : Function<T>(IntegerColumnType()) { - override fun toSQL(queryBuilder: QueryBuilder): String = "ABS(${expr.toSQL(queryBuilder)})" -} - -fun <T : Comparable<T>> greaterOf(col1: ExpressionWithColumnType<T>, col2: ExpressionWithColumnType<T>): Expression<T> = - with(SqlExpressionBuilder) { - case(col1) - .When(col1.greater(col2), col1) - .Else(col2) - } - +package io.dico.parcels2.storage.exposed
+
+import org.jetbrains.exposed.sql.*
+import org.jetbrains.exposed.sql.Function
+import org.jetbrains.exposed.sql.statements.InsertStatement
+import org.jetbrains.exposed.sql.transactions.TransactionManager
+
+class UpsertStatement<Key : Any>(table: Table, conflictColumn: Column<*>? = null, conflictIndex: Index? = null) : InsertStatement<Key>(table, false) {
+ val indexName: String
+ val indexColumns: List<Column<*>>
+
+ init {
+ when {
+ conflictIndex != null -> {
+ indexName = conflictIndex.indexName
+ indexColumns = conflictIndex.columns
+ }
+ conflictColumn != null -> {
+ indexName = conflictColumn.name
+ indexColumns = listOf(conflictColumn)
+ }
+ else -> throw IllegalArgumentException()
+ }
+ }
+
+ override fun prepareSQL(transaction: Transaction) = buildString {
+ append(super.prepareSQL(transaction))
+
+ val dialect = transaction.db.vendor
+ if (dialect == "postgresql") {
+
+ append(" ON CONFLICT(")
+ append(indexName)
+ append(") DO UPDATE SET ")
+
+ values.keys.filter { it !in indexColumns }.joinTo(this) { "${transaction.identity(it)}=EXCLUDED.${transaction.identity(it)}" }
+
+ } else {
+
+ append(" ON DUPLICATE KEY UPDATE ")
+ values.keys.filter { it !in indexColumns }.joinTo(this) { "${transaction.identity(it)}=VALUES(${transaction.identity(it)})" }
+
+ }
+ }
+
+}
+
+inline fun <T : Table> T.upsert(conflictColumn: Column<*>? = null, conflictIndex: Index? = null, body: T.(UpsertStatement<Number>) -> Unit) =
+ UpsertStatement<Number>(this, conflictColumn, conflictIndex).apply {
+ body(this)
+ execute(TransactionManager.current())
+ }
+
+fun Table.indexR(customIndexName: String? = null, isUnique: Boolean = false, vararg columns: Column<*>): Index {
+ val index = Index(columns.toList(), isUnique, customIndexName)
+ indices.add(index)
+ return index
+}
+
+fun Table.uniqueIndexR(customIndexName: String? = null, vararg columns: Column<*>): Index = indexR(customIndexName, true, *columns)
+
+fun <T : Int?> ExpressionWithColumnType<T>.abs(): Function<T> = Abs(this)
+
+class Abs<T : Int?>(val expr: Expression<T>) : Function<T>(IntegerColumnType()) {
+ override fun toSQL(queryBuilder: QueryBuilder): String = "ABS(${expr.toSQL(queryBuilder)})"
+}
+
+fun <T : Comparable<T>> greaterOf(col1: ExpressionWithColumnType<T>, col2: ExpressionWithColumnType<T>): Expression<T> =
+ with(SqlExpressionBuilder) {
+ case(col1)
+ .When(col1.greater(col2), col1)
+ .Else(col2)
+ }
+
diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt index 6f6ad6b..8428b3a 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt @@ -1,165 +1,165 @@ -@file:Suppress("NOTHING_TO_INLINE", "PARAMETER_NAME_CHANGED_ON_OVERRIDE", "unused", "MemberVisibilityCanBePrivate") - -package io.dico.parcels2.storage.exposed - -import io.dico.parcels2.ParcelId -import io.dico.parcels2.ParcelWorldId -import io.dico.parcels2.PlayerProfile -import io.dico.parcels2.storage.toByteArray -import io.dico.parcels2.storage.toUUID -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.statements.UpdateBuilder -import org.joda.time.DateTime -import java.util.UUID - -abstract class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj>, QueryObj>(tableName: String, columnName: String) - : Table(tableName) { - val id = integer(columnName).autoIncrement().primaryKey() - - @Suppress("UNCHECKED_CAST") - inline val table: TableT - get() = this as TableT - - internal inline fun getId(where: SqlExpressionBuilder.(TableT) -> Op<Boolean>): Int? { - return select { where(table) }.firstOrNull()?.let { it[id] } - } - - internal inline fun getOrInitId(getId: () -> Int?, noinline body: TableT.(UpdateBuilder<*>) -> Unit, objName: () -> String): Int { - return getId() ?: table.insertIgnore(body)[id] ?: getId() - ?: throw ExposedDatabaseException("This should not happen - failed to insert ${objName()} and get its number") - } - - abstract fun getId(obj: QueryObj): Int? - abstract fun getOrInitId(obj: QueryObj): Int - fun getItem(id: Int): QueryObj? = select { this@IdTransactionsTable.id eq id }.firstOrNull()?.let { getItem(it) } - abstract fun getItem(row: ResultRow): QueryObj? - - fun getId(obj: QueryObj, init: Boolean): Int? = if (init) getOrInitId(obj) else getId(obj) -} - -object WorldsT : IdTransactionsTable<WorldsT, ParcelWorldId>("parcels_worlds", "world_id") { - val name = varchar("name", 50) - val uid = binary("uid", 16).nullable() - val creation_time = datetime("creation_time").nullable() - val index_name = uniqueIndexR("index_name", name) - val index_uid = uniqueIndexR("index_uid", uid) - - internal inline fun getId(worldName: String, binaryUid: ByteArray?): Int? = getId { (name eq worldName).let { if (binaryUid == null) it else it or (uid eq binaryUid) } } - internal inline fun getId(worldName: String, uid: UUID?): Int? = getId(worldName, uid?.toByteArray()) - internal inline fun getOrInitId(worldName: String, worldUid: UUID?): Int = worldUid?.toByteArray().let { binaryUid -> - return getOrInitId( - { getId(worldName, binaryUid) }, - { it[name] = worldName; it[uid] = binaryUid }, - { "world named $worldName" }) - } - - override fun getId(world: ParcelWorldId): Int? = getId(world.name, world.uid) - override fun getOrInitId(world: ParcelWorldId): Int = getOrInitId(world.name, world.uid) - - override fun getItem(row: ResultRow): ParcelWorldId { - return ParcelWorldId(row[name], row[uid]?.toUUID()) - } - - fun getWorldCreationTime(worldId: ParcelWorldId): DateTime? { - val id = getId(worldId) ?: return null - return select { WorldsT.id eq id }.firstOrNull()?.let { it[WorldsT.creation_time] } - } - - fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime) { - val id = getOrInitId(worldId) - update({ WorldsT.id eq id }) { - it[WorldsT.creation_time] = time - } - } -} - -object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("parcels", "parcel_id") { - val world_id = integer("world_id").references(WorldsT.id) - val px = integer("px") - val pz = integer("pz") - val owner_id = integer("owner_id").references(ProfilesT.id).nullable() - val sign_oudated = bool("sign_outdated").default(false) - val claim_time = datetime("claim_time").nullable() - val index_location = uniqueIndexR("index_location", world_id, px, pz) - - private inline fun getId(worldId: Int, parcelX: Int, parcelZ: Int): Int? = getId { world_id.eq(worldId) and px.eq(parcelX) and pz.eq(parcelZ) } - private inline fun getId(worldName: String, worldUid: UUID?, parcelX: Int, parcelZ: Int): Int? = WorldsT.getId(worldName, worldUid)?.let { getId(it, parcelX, parcelZ) } - private inline fun getOrInitId(worldName: String, worldUid: UUID?, parcelX: Int, parcelZ: Int): Int { - val worldId = WorldsT.getOrInitId(worldName, worldUid) - return getOrInitId( - { getId(worldId, parcelX, parcelZ) }, - { it[world_id] = worldId; it[px] = parcelX; it[pz] = parcelZ }, - { "parcel at $worldName($parcelX, $parcelZ)" }) - } - - override fun getId(parcel: ParcelId): Int? = getId(parcel.worldId.name, parcel.worldId.uid, parcel.x, parcel.z) - override fun getOrInitId(parcel: ParcelId): Int = getOrInitId(parcel.worldId.name, parcel.worldId.uid, parcel.x, parcel.z) - - private inline fun getRow(id: Int): ResultRow? = select { ParcelsT.id eq id }.firstOrNull() - fun getRow(parcel: ParcelId): ResultRow? = getId(parcel)?.let { getRow(it) } - - override fun getItem(row: ResultRow): ParcelId? { - val worldId = row[world_id] - val world = WorldsT.getItem(worldId) ?: return null - return ParcelId(world, row[px], row[pz]) - } -} - -object ProfilesT : IdTransactionsTable<ProfilesT, PlayerProfile>("parcels_profiles", "owner_id") { - val uuid = binary("uuid", 16).nullable() - val name = varchar("name", 32).nullable() - - // MySQL dialect MUST permit multiple null values for this to work. Server SQL does not allow this. That dialect is shit anyway. - val uuid_constraint = uniqueIndexR("uuid_constraint", uuid) - val index_pair = uniqueIndexR("index_pair", uuid, name) - - - private inline fun getId(binaryUuid: ByteArray) = getId { uuid eq binaryUuid } - private inline fun getId(uuid: UUID) = getId(uuid.toByteArray()) - private inline fun getId(nameIn: String) = getId { uuid.isNull() and (name.lowerCase() eq nameIn.toLowerCase()) } - private inline fun getRealId(nameIn: String) = getId { uuid.isNotNull() and (name.lowerCase() eq nameIn.toLowerCase()) } - - private inline fun getOrInitId(uuid: UUID, name: String?) = uuid.toByteArray().let { binaryUuid -> - getOrInitId( - { getId(binaryUuid) }, - { it[this@ProfilesT.uuid] = binaryUuid; it[this@ProfilesT.name] = name }, - { "profile(uuid = $uuid, name = $name)" }) - } - - private inline fun getOrInitId(name: String) = getOrInitId( - { getId(name) }, - { it[ProfilesT.name] = name }, - { "owner(name = $name)" }) - - - override fun getId(profile: PlayerProfile): Int? = when (profile) { - is PlayerProfile.Real -> getId(profile.uuid) - is PlayerProfile.Fake -> getId(profile.name) - is PlayerProfile.Unresolved -> getRealId(profile.name) - else -> throw IllegalArgumentException() - } - - override fun getOrInitId(profile: PlayerProfile): Int = when (profile) { - is PlayerProfile.Real -> getOrInitId(profile.uuid, profile.nameOrBukkitName) - is PlayerProfile.Fake -> getOrInitId(profile.name) - else -> throw IllegalArgumentException() // Unresolved profiles cannot be added to the database - } - - override fun getItem(row: ResultRow): PlayerProfile { - return PlayerProfile(row[uuid]?.toUUID(), row[name]) - } - - fun getRealItem(id: Int): PlayerProfile.Real? { - return getItem(id) as? PlayerProfile.Real - } - - /* - fun updatePlayerProfile(profile: PlayerProfile.Real) { - update({ uuid eq profile.uuid.toByteArray() }) { - it[name] = profile.nameOrBukkitName - } - }*/ - -} - +@file:Suppress("NOTHING_TO_INLINE", "PARAMETER_NAME_CHANGED_ON_OVERRIDE", "unused", "MemberVisibilityCanBePrivate")
+
+package io.dico.parcels2.storage.exposed
+
+import io.dico.parcels2.ParcelId
+import io.dico.parcels2.ParcelWorldId
+import io.dico.parcels2.PlayerProfile
+import io.dico.parcels2.storage.toByteArray
+import io.dico.parcels2.storage.toUUID
+import org.jetbrains.exposed.sql.*
+import org.jetbrains.exposed.sql.statements.UpdateBuilder
+import org.joda.time.DateTime
+import java.util.UUID
+
+abstract class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj>, QueryObj>(tableName: String, columnName: String)
+ : Table(tableName) {
+ val id = integer(columnName).autoIncrement().primaryKey()
+
+ @Suppress("UNCHECKED_CAST")
+ inline val table: TableT
+ get() = this as TableT
+
+ internal inline fun getId(where: SqlExpressionBuilder.(TableT) -> Op<Boolean>): Int? {
+ return select { where(table) }.firstOrNull()?.let { it[id] }
+ }
+
+ internal inline fun getOrInitId(getId: () -> Int?, noinline body: TableT.(UpdateBuilder<*>) -> Unit, objName: () -> String): Int {
+ return getId() ?: table.insertIgnore(body)[id] ?: getId()
+ ?: throw ExposedDatabaseException("This should not happen - failed to insert ${objName()} and get its number")
+ }
+
+ abstract fun getId(obj: QueryObj): Int?
+ abstract fun getOrInitId(obj: QueryObj): Int
+ fun getItem(id: Int): QueryObj? = select { this@IdTransactionsTable.id eq id }.firstOrNull()?.let { getItem(it) }
+ abstract fun getItem(row: ResultRow): QueryObj?
+
+ fun getId(obj: QueryObj, init: Boolean): Int? = if (init) getOrInitId(obj) else getId(obj)
+}
+
+object WorldsT : IdTransactionsTable<WorldsT, ParcelWorldId>("parcels_worlds", "world_id") {
+ val name = varchar("name", 50)
+ val uid = binary("uid", 16).nullable()
+ val creation_time = datetime("creation_time").nullable()
+ val index_name = uniqueIndexR("index_name", name)
+ val index_uid = uniqueIndexR("index_uid", uid)
+
+ internal inline fun getId(worldName: String, binaryUid: ByteArray?): Int? = getId { (name eq worldName).let { if (binaryUid == null) it else it or (uid eq binaryUid) } }
+ internal inline fun getId(worldName: String, uid: UUID?): Int? = getId(worldName, uid?.toByteArray())
+ internal inline fun getOrInitId(worldName: String, worldUid: UUID?): Int = worldUid?.toByteArray().let { binaryUid ->
+ return getOrInitId(
+ { getId(worldName, binaryUid) },
+ { it[name] = worldName; it[uid] = binaryUid },
+ { "world named $worldName" })
+ }
+
+ override fun getId(world: ParcelWorldId): Int? = getId(world.name, world.uid)
+ override fun getOrInitId(world: ParcelWorldId): Int = getOrInitId(world.name, world.uid)
+
+ override fun getItem(row: ResultRow): ParcelWorldId {
+ return ParcelWorldId(row[name], row[uid]?.toUUID())
+ }
+
+ fun getWorldCreationTime(worldId: ParcelWorldId): DateTime? {
+ val id = getId(worldId) ?: return null
+ return select { WorldsT.id eq id }.firstOrNull()?.let { it[WorldsT.creation_time] }
+ }
+
+ fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime) {
+ val id = getOrInitId(worldId)
+ update({ WorldsT.id eq id }) {
+ it[WorldsT.creation_time] = time
+ }
+ }
+}
+
+object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("parcels", "parcel_id") {
+ val world_id = integer("world_id").references(WorldsT.id)
+ val px = integer("px")
+ val pz = integer("pz")
+ val owner_id = integer("owner_id").references(ProfilesT.id).nullable()
+ val sign_oudated = bool("sign_outdated").default(false)
+ val claim_time = datetime("claim_time").nullable()
+ val index_location = uniqueIndexR("index_location", world_id, px, pz)
+
+ private inline fun getId(worldId: Int, parcelX: Int, parcelZ: Int): Int? = getId { world_id.eq(worldId) and px.eq(parcelX) and pz.eq(parcelZ) }
+ private inline fun getId(worldName: String, worldUid: UUID?, parcelX: Int, parcelZ: Int): Int? = WorldsT.getId(worldName, worldUid)?.let { getId(it, parcelX, parcelZ) }
+ private inline fun getOrInitId(worldName: String, worldUid: UUID?, parcelX: Int, parcelZ: Int): Int {
+ val worldId = WorldsT.getOrInitId(worldName, worldUid)
+ return getOrInitId(
+ { getId(worldId, parcelX, parcelZ) },
+ { it[world_id] = worldId; it[px] = parcelX; it[pz] = parcelZ },
+ { "parcel at $worldName($parcelX, $parcelZ)" })
+ }
+
+ override fun getId(parcel: ParcelId): Int? = getId(parcel.worldId.name, parcel.worldId.uid, parcel.x, parcel.z)
+ override fun getOrInitId(parcel: ParcelId): Int = getOrInitId(parcel.worldId.name, parcel.worldId.uid, parcel.x, parcel.z)
+
+ private inline fun getRow(id: Int): ResultRow? = select { ParcelsT.id eq id }.firstOrNull()
+ fun getRow(parcel: ParcelId): ResultRow? = getId(parcel)?.let { getRow(it) }
+
+ override fun getItem(row: ResultRow): ParcelId? {
+ val worldId = row[world_id]
+ val world = WorldsT.getItem(worldId) ?: return null
+ return ParcelId(world, row[px], row[pz])
+ }
+}
+
+object ProfilesT : IdTransactionsTable<ProfilesT, PlayerProfile>("parcels_profiles", "owner_id") {
+ val uuid = binary("uuid", 16).nullable()
+ val name = varchar("name", 32).nullable()
+
+ // MySQL dialect MUST permit multiple null values for this to work. Server SQL does not allow this. That dialect is shit anyway.
+ val uuid_constraint = uniqueIndexR("uuid_constraint", uuid)
+ val index_pair = uniqueIndexR("index_pair", uuid, name)
+
+
+ private inline fun getId(binaryUuid: ByteArray) = getId { uuid eq binaryUuid }
+ private inline fun getId(uuid: UUID) = getId(uuid.toByteArray())
+ private inline fun getId(nameIn: String) = getId { uuid.isNull() and (name.lowerCase() eq nameIn.toLowerCase()) }
+ private inline fun getRealId(nameIn: String) = getId { uuid.isNotNull() and (name.lowerCase() eq nameIn.toLowerCase()) }
+
+ private inline fun getOrInitId(uuid: UUID, name: String?) = uuid.toByteArray().let { binaryUuid ->
+ getOrInitId(
+ { getId(binaryUuid) },
+ { it[this@ProfilesT.uuid] = binaryUuid; it[this@ProfilesT.name] = name },
+ { "profile(uuid = $uuid, name = $name)" })
+ }
+
+ private inline fun getOrInitId(name: String) = getOrInitId(
+ { getId(name) },
+ { it[ProfilesT.name] = name },
+ { "owner(name = $name)" })
+
+
+ override fun getId(profile: PlayerProfile): Int? = when (profile) {
+ is PlayerProfile.Real -> getId(profile.uuid)
+ is PlayerProfile.Fake -> getId(profile.name)
+ is PlayerProfile.Unresolved -> getRealId(profile.name)
+ else -> throw IllegalArgumentException()
+ }
+
+ override fun getOrInitId(profile: PlayerProfile): Int = when (profile) {
+ is PlayerProfile.Real -> getOrInitId(profile.uuid, profile.nameOrBukkitName)
+ is PlayerProfile.Fake -> getOrInitId(profile.name)
+ else -> throw IllegalArgumentException() // Unresolved profiles cannot be added to the database
+ }
+
+ override fun getItem(row: ResultRow): PlayerProfile {
+ return PlayerProfile(row[uuid]?.toUUID(), row[name])
+ }
+
+ fun getRealItem(id: Int): PlayerProfile.Real? {
+ return getItem(id) as? PlayerProfile.Real
+ }
+
+ /*
+ fun updatePlayerProfile(profile: PlayerProfile.Real) {
+ update({ uuid eq profile.uuid.toByteArray() }) {
+ it[name] = profile.nameOrBukkitName
+ }
+ }*/
+
+}
+
// val ParcelsWithOptionsT = ParcelsT.join(ParcelOptionsT, JoinType.INNER, onColumn = ParcelsT.id, otherColumn = ParcelOptionsT.parcel_id)
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt index b9d16fc..26cfc7a 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt @@ -1,111 +1,111 @@ -@file:Suppress("PropertyName", "LocalVariableName", "NOTHING_TO_INLINE") - -package io.dico.parcels2.storage.exposed - -import io.dico.parcels2.* -import io.dico.parcels2.Privilege.DEFAULT -import io.dico.parcels2.util.ext.alsoIfTrue -import kotlinx.coroutines.channels.SendChannel -import org.jetbrains.exposed.sql.* - -object PrivilegesLocalT : PrivilegesTable<ParcelId>("parcels_privilege_local", ParcelsT) -object PrivilegesGlobalT : PrivilegesTable<PlayerProfile>("parcels_privilege_global", ProfilesT) - -object ParcelOptionsT : Table("parcels_options") { - val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE) - val interact_bitmask = binary("interact_bitmask", 4) -} - -typealias PrivilegesSendChannel<AttachT> = SendChannel<Pair<AttachT, PrivilegesHolder>> - -sealed class PrivilegesTable<AttachT>(name: String, val idTable: IdTransactionsTable<*, AttachT>) : Table(name) { - val attach_id = integer("attach_id").references(idTable.id, ReferenceOption.CASCADE) - val profile_id = integer("profile_id").references(ProfilesT.id, ReferenceOption.CASCADE) - val privilege = integer("privilege") - val index_pair = uniqueIndexR("index_pair", attach_id, profile_id) - - fun setPrivilege(attachedOn: AttachT, player: PlayerProfile.Real, privilege: Privilege) { - privilege.requireNonTransient() - - if (privilege == DEFAULT) { - val player_id = ProfilesT.getId(player) ?: return - idTable.getId(attachedOn)?.let { holder -> - deleteWhere { (attach_id eq holder) and (profile_id eq player_id) } - } - return - } - - val holder = idTable.getOrInitId(attachedOn) - val player_id = ProfilesT.getOrInitId(player) - upsert(conflictIndex = index_pair) { - it[attach_id] = holder - it[profile_id] = player_id - it[this.privilege] = privilege.number - } - } - - fun readPrivileges(id: Int): PrivilegesHolder? { - val list = slice(profile_id, privilege).select { attach_id eq id } - val result = PrivilegesHolder() - for (row in list) { - val profile = ProfilesT.getRealItem(row[profile_id]) ?: continue - result.setRawStoredPrivilege(profile, Privilege.getByNumber(row[privilege]) ?: continue) - } - return result - } - - fun sendAllPrivilegesH(channel: PrivilegesSendChannel<AttachT>) { - val iterator = selectAll().orderBy(attach_id).iterator() - - if (iterator.hasNext()) { - var row = iterator.next() - var id: Int = row[attach_id] - var attach: AttachT? = null - var map: PrivilegesHolder? = null - - fun initAttachAndMap() { - attach = idTable.getItem(id) - map = attach?.let { PrivilegesHolder() } - } - - fun sendIfPresent() { - if (attach != null && map != null) { - channel.offer(attach!! to map!!) - } - attach = null - map = null - } - - initAttachAndMap() - - do { - val rowId = row[attach_id] - if (rowId != id) { - sendIfPresent() - id = rowId - initAttachAndMap() - } - - if (attach == null) { - continue // owner not found for this owner id - } - - val profile = ProfilesT.getRealItem(row[profile_id]) - if (profile == null) { - logger.error("Privilege from database is null, id ${row[profile_id]}") - continue - } - val privilege = Privilege.getByNumber(row[privilege]) - if (privilege == null) { - logger.error("Privilege from database is null, number ${row[this.privilege]}") - continue - } - map!!.setRawStoredPrivilege(profile, privilege) - - } while (iterator.hasNext().alsoIfTrue { row = iterator.next() }) - - sendIfPresent() - } - } - -} +@file:Suppress("PropertyName", "LocalVariableName", "NOTHING_TO_INLINE")
+
+package io.dico.parcels2.storage.exposed
+
+import io.dico.parcels2.*
+import io.dico.parcels2.Privilege.DEFAULT
+import io.dico.parcels2.util.ext.alsoIfTrue
+import kotlinx.coroutines.channels.SendChannel
+import org.jetbrains.exposed.sql.*
+
+object PrivilegesLocalT : PrivilegesTable<ParcelId>("parcels_privilege_local", ParcelsT)
+object PrivilegesGlobalT : PrivilegesTable<PlayerProfile>("parcels_privilege_global", ProfilesT)
+
+object ParcelOptionsT : Table("parcels_options") {
+ val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE)
+ val interact_bitmask = binary("interact_bitmask", 4)
+}
+
+typealias PrivilegesSendChannel<AttachT> = SendChannel<Pair<AttachT, PrivilegesHolder>>
+
+sealed class PrivilegesTable<AttachT>(name: String, val idTable: IdTransactionsTable<*, AttachT>) : Table(name) {
+ val attach_id = integer("attach_id").references(idTable.id, ReferenceOption.CASCADE)
+ val profile_id = integer("profile_id").references(ProfilesT.id, ReferenceOption.CASCADE)
+ val privilege = integer("privilege")
+ val index_pair = uniqueIndexR("index_pair", attach_id, profile_id)
+
+ fun setPrivilege(attachedOn: AttachT, player: PlayerProfile.Real, privilege: Privilege) {
+ privilege.requireNonTransient()
+
+ if (privilege == DEFAULT) {
+ val player_id = ProfilesT.getId(player) ?: return
+ idTable.getId(attachedOn)?.let { holder ->
+ deleteWhere { (attach_id eq holder) and (profile_id eq player_id) }
+ }
+ return
+ }
+
+ val holder = idTable.getOrInitId(attachedOn)
+ val player_id = ProfilesT.getOrInitId(player)
+ upsert(conflictIndex = index_pair) {
+ it[attach_id] = holder
+ it[profile_id] = player_id
+ it[this.privilege] = privilege.number
+ }
+ }
+
+ fun readPrivileges(id: Int): PrivilegesHolder? {
+ val list = slice(profile_id, privilege).select { attach_id eq id }
+ val result = PrivilegesHolder()
+ for (row in list) {
+ val profile = ProfilesT.getRealItem(row[profile_id]) ?: continue
+ result.setRawStoredPrivilege(profile, Privilege.getByNumber(row[privilege]) ?: continue)
+ }
+ return result
+ }
+
+ fun sendAllPrivilegesH(channel: PrivilegesSendChannel<AttachT>) {
+ val iterator = selectAll().orderBy(attach_id).iterator()
+
+ if (iterator.hasNext()) {
+ var row = iterator.next()
+ var id: Int = row[attach_id]
+ var attach: AttachT? = null
+ var map: PrivilegesHolder? = null
+
+ fun initAttachAndMap() {
+ attach = idTable.getItem(id)
+ map = attach?.let { PrivilegesHolder() }
+ }
+
+ fun sendIfPresent() {
+ if (attach != null && map != null) {
+ channel.offer(attach!! to map!!)
+ }
+ attach = null
+ map = null
+ }
+
+ initAttachAndMap()
+
+ do {
+ val rowId = row[attach_id]
+ if (rowId != id) {
+ sendIfPresent()
+ id = rowId
+ initAttachAndMap()
+ }
+
+ if (attach == null) {
+ continue // owner not found for this owner id
+ }
+
+ val profile = ProfilesT.getRealItem(row[profile_id])
+ if (profile == null) {
+ logger.error("Privilege from database is null, id ${row[profile_id]}")
+ continue
+ }
+ val privilege = Privilege.getByNumber(row[privilege])
+ if (privilege == null) {
+ logger.error("Privilege from database is null, number ${row[this.privilege]}")
+ continue
+ }
+ map!!.setRawStoredPrivilege(profile, privilege)
+
+ } while (iterator.hasNext().alsoIfTrue { row = iterator.next() })
+
+ sendIfPresent()
+ }
+ }
+
+}
diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt index acc7c5e..a512f2a 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt @@ -1,9 +1,9 @@ -package io.dico.parcels2.storage.migration - -import io.dico.parcels2.storage.Storage -import kotlinx.coroutines.Job - -interface Migration { - fun migrateTo(storage: Storage): Job -} - +package io.dico.parcels2.storage.migration
+
+import io.dico.parcels2.storage.Storage
+import kotlinx.coroutines.Job
+
+interface Migration {
+ fun migrateTo(storage: Storage): Job
+}
+
diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt index 831fe42..954da5d 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt @@ -1,118 +1,118 @@ -@file:Suppress("RedundantSuspendModifier", "DEPRECATION") - -package io.dico.parcels2.storage.migration.plotme - -import com.zaxxer.hikari.HikariDataSource -import io.dico.parcels2.* -import io.dico.parcels2.options.PlotmeMigrationOptions -import io.dico.parcels2.storage.Storage -import io.dico.parcels2.storage.exposed.abs -import io.dico.parcels2.storage.exposed.greaterOf -import io.dico.parcels2.storage.migration.Migration -import io.dico.parcels2.storage.migration.plotme.PlotmeTables.PlotmePlotPlayerMap -import io.dico.parcels2.storage.migration.plotme.PlotmeTables.PlotmeTable -import io.dico.parcels2.storage.toUUID -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import kotlinx.coroutines.newFixedThreadPoolContext -import org.jetbrains.exposed.sql.* -import org.slf4j.LoggerFactory -import java.sql.Blob -import java.util.UUID -import javax.sql.DataSource - -class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration { - private var dataSource: DataSource? = null - private var database: Database? = null - private var isShutdown: Boolean = false - private val mlogger = LoggerFactory.getLogger("PlotMe Migrator") - private val tables = PlotmeTables(options.tableNamesUppercase) - val dispatcher = newFixedThreadPoolContext(1, "PlotMe Migration Thread") - - private fun <T> transaction(statement: Transaction.() -> T) = org.jetbrains.exposed.sql.transactions.transaction(database!!, statement) - - override fun migrateTo(storage: Storage): Job { - return launch(dispatcher) { - init() - doWork(storage) - shutdown() - } - } - - fun init() { - if (isShutdown || database != null) throw IllegalStateException() - dataSource = options.storage.getDataSourceFactory()!!() - database = Database.connect(dataSource!!) - } - - fun shutdown() { - if (isShutdown) throw IllegalStateException() - dataSource?.let { - (it as? HikariDataSource)?.close() - } - database = null - isShutdown = true - } - - suspend fun doWork(target: Storage) = with (tables) { - val exit = transaction { - (!PlotmePlots.exists()).also { - if (it) mlogger.warn("Plotme tables don't appear to exist. Exiting.") - } - } - if (exit) return - - val worldCache = options.worldsFromTo.mapValues { ParcelWorldId(it.value) } - - fun getParcelId(table: PlotmeTable, row: ResultRow): ParcelId? { - val world = worldCache[row[table.world_name]] ?: return null - return ParcelId(world, row[table.px], row[table.pz]) - } - - fun PlotmePlotPlayerMap.transmitPlotmeAddedTable(kind: Privilege) { - selectAll().forEach { row -> - val parcel = getParcelId(this, row) ?: return@forEach - val profile = PrivilegeKey.safe(row[player_uuid]?.toUUID(), row[player_name]) ?: return@forEach - target.setLocalPrivilege(parcel, profile, kind) - } - } - - mlogger.info("Transmitting data from plotmeplots table") - var count = 0 - transaction { - - PlotmePlots.selectAll() - .orderBy(PlotmePlots.world_name) - .orderBy(greaterOf(PlotmePlots.px.abs(), PlotmePlots.pz.abs())) - .forEach { row -> - val parcel = getParcelId(PlotmePlots, row) ?: return@forEach - val owner = PlayerProfile.safe(row[PlotmePlots.owner_uuid]?.toUUID(), row[PlotmePlots.owner_name]) - target.setParcelOwner(parcel, owner) - target.setParcelOwnerSignOutdated(parcel, true) - ++count - } - } - - mlogger.info("Transmitting data from plotmeallowed table") - transaction { - PlotmeAllowed.transmitPlotmeAddedTable(Privilege.CAN_BUILD) - } - - mlogger.info("Transmitting data from plotmedenied table") - transaction { - PlotmeDenied.transmitPlotmeAddedTable(Privilege.BANNED) - } - - mlogger.warn("Data has been **transmitted**. $count plots were migrated to the parcels database.") - mlogger.warn("Loading parcel data might take a while as enqueued transactions from this migration are completed.") - } - - private fun Blob.toUUID(): UUID? { - val ba = ByteArray(16) - val count = binaryStream.read(ba, 0, 16) - if (count < 16) return null - return ba.toUUID() - } - - +@file:Suppress("RedundantSuspendModifier", "DEPRECATION")
+
+package io.dico.parcels2.storage.migration.plotme
+
+import com.zaxxer.hikari.HikariDataSource
+import io.dico.parcels2.*
+import io.dico.parcels2.options.PlotmeMigrationOptions
+import io.dico.parcels2.storage.Storage
+import io.dico.parcels2.storage.exposed.abs
+import io.dico.parcels2.storage.exposed.greaterOf
+import io.dico.parcels2.storage.migration.Migration
+import io.dico.parcels2.storage.migration.plotme.PlotmeTables.PlotmePlotPlayerMap
+import io.dico.parcels2.storage.migration.plotme.PlotmeTables.PlotmeTable
+import io.dico.parcels2.storage.toUUID
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.newFixedThreadPoolContext
+import org.jetbrains.exposed.sql.*
+import org.slf4j.LoggerFactory
+import java.sql.Blob
+import java.util.UUID
+import javax.sql.DataSource
+
+class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration {
+ private var dataSource: DataSource? = null
+ private var database: Database? = null
+ private var isShutdown: Boolean = false
+ private val mlogger = LoggerFactory.getLogger("PlotMe Migrator")
+ private val tables = PlotmeTables(options.tableNamesUppercase)
+ val dispatcher = newFixedThreadPoolContext(1, "PlotMe Migration Thread")
+
+ private fun <T> transaction(statement: Transaction.() -> T) = org.jetbrains.exposed.sql.transactions.transaction(database!!, statement)
+
+ override fun migrateTo(storage: Storage): Job {
+ return launch(dispatcher) {
+ init()
+ doWork(storage)
+ shutdown()
+ }
+ }
+
+ fun init() {
+ if (isShutdown || database != null) throw IllegalStateException()
+ dataSource = options.storage.getDataSourceFactory()!!()
+ database = Database.connect(dataSource!!)
+ }
+
+ fun shutdown() {
+ if (isShutdown) throw IllegalStateException()
+ dataSource?.let {
+ (it as? HikariDataSource)?.close()
+ }
+ database = null
+ isShutdown = true
+ }
+
+ suspend fun doWork(target: Storage) = with (tables) {
+ val exit = transaction {
+ (!PlotmePlots.exists()).also {
+ if (it) mlogger.warn("Plotme tables don't appear to exist. Exiting.")
+ }
+ }
+ if (exit) return
+
+ val worldCache = options.worldsFromTo.mapValues { ParcelWorldId(it.value) }
+
+ fun getParcelId(table: PlotmeTable, row: ResultRow): ParcelId? {
+ val world = worldCache[row[table.world_name]] ?: return null
+ return ParcelId(world, row[table.px], row[table.pz])
+ }
+
+ fun PlotmePlotPlayerMap.transmitPlotmeAddedTable(kind: Privilege) {
+ selectAll().forEach { row ->
+ val parcel = getParcelId(this, row) ?: return@forEach
+ val profile = PrivilegeKey.safe(row[player_uuid]?.toUUID(), row[player_name]) ?: return@forEach
+ target.setLocalPrivilege(parcel, profile, kind)
+ }
+ }
+
+ mlogger.info("Transmitting data from plotmeplots table")
+ var count = 0
+ transaction {
+
+ PlotmePlots.selectAll()
+ .orderBy(PlotmePlots.world_name)
+ .orderBy(greaterOf(PlotmePlots.px.abs(), PlotmePlots.pz.abs()))
+ .forEach { row ->
+ val parcel = getParcelId(PlotmePlots, row) ?: return@forEach
+ val owner = PlayerProfile.safe(row[PlotmePlots.owner_uuid]?.toUUID(), row[PlotmePlots.owner_name])
+ target.setParcelOwner(parcel, owner)
+ target.setParcelOwnerSignOutdated(parcel, true)
+ ++count
+ }
+ }
+
+ mlogger.info("Transmitting data from plotmeallowed table")
+ transaction {
+ PlotmeAllowed.transmitPlotmeAddedTable(Privilege.CAN_BUILD)
+ }
+
+ mlogger.info("Transmitting data from plotmedenied table")
+ transaction {
+ PlotmeDenied.transmitPlotmeAddedTable(Privilege.BANNED)
+ }
+
+ mlogger.warn("Data has been **transmitted**. $count plots were migrated to the parcels database.")
+ mlogger.warn("Loading parcel data might take a while as enqueued transactions from this migration are completed.")
+ }
+
+ private fun Blob.toUUID(): UUID? {
+ val ba = ByteArray(16)
+ val count = binaryStream.read(ba, 0, 16)
+ if (count < 16) return null
+ return ba.toUUID()
+ }
+
+
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeTables.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeTables.kt index dc788c8..b276e11 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeTables.kt @@ -1,31 +1,31 @@ -package io.dico.parcels2.storage.migration.plotme - -import org.jetbrains.exposed.sql.Table - -class PlotmeTables(val uppercase: Boolean) { - fun String.toCorrectCase() = if (uppercase) this else toLowerCase() - - val PlotmePlots = PlotmePlotsT() - val PlotmeAllowed = PlotmeAllowedT() - val PlotmeDenied = PlotmeDeniedT() - - inner abstract class PlotmeTable(name: String) : Table(name) { - val px = integer("idX").primaryKey() - val pz = integer("idZ").primaryKey() - val world_name = varchar("world", 32).primaryKey() - } - - inner abstract class PlotmePlotPlayerMap(name: String) : PlotmeTable(name) { - val player_name = varchar("player", 32) - val player_uuid = blob("playerid").nullable() - } - - inner class PlotmePlotsT : PlotmeTable("plotmePlots".toCorrectCase()) { - val owner_name = varchar("owner", 32) - val owner_uuid = blob("ownerid").nullable() - } - - inner class PlotmeAllowedT : PlotmePlotPlayerMap("plotmeAllowed".toCorrectCase()) - inner class PlotmeDeniedT : PlotmePlotPlayerMap("plotmeDenied".toCorrectCase()) -} - +package io.dico.parcels2.storage.migration.plotme
+
+import org.jetbrains.exposed.sql.Table
+
+class PlotmeTables(val uppercase: Boolean) {
+ fun String.toCorrectCase() = if (uppercase) this else toLowerCase()
+
+ val PlotmePlots = PlotmePlotsT()
+ val PlotmeAllowed = PlotmeAllowedT()
+ val PlotmeDenied = PlotmeDeniedT()
+
+ inner abstract class PlotmeTable(name: String) : Table(name) {
+ val px = integer("idX").primaryKey()
+ val pz = integer("idZ").primaryKey()
+ val world_name = varchar("world", 32).primaryKey()
+ }
+
+ inner abstract class PlotmePlotPlayerMap(name: String) : PlotmeTable(name) {
+ val player_name = varchar("player", 32)
+ val player_uuid = blob("playerid").nullable()
+ }
+
+ inner class PlotmePlotsT : PlotmeTable("plotmePlots".toCorrectCase()) {
+ val owner_name = varchar("owner", 32)
+ val owner_uuid = blob("ownerid").nullable()
+ }
+
+ inner class PlotmeAllowedT : PlotmePlotPlayerMap("plotmeAllowed".toCorrectCase())
+ inner class PlotmeDeniedT : PlotmePlotPlayerMap("plotmeDenied".toCorrectCase())
+}
+
|