Refactor and improve a lot of the API. Move default implementations into a package. Reformatting.
This commit is contained in:
@@ -1,11 +1,8 @@
|
||||
package io.dico.parcels2.storage
|
||||
|
||||
import io.dico.parcels2.AddedStatus
|
||||
import io.dico.parcels2.Parcel
|
||||
import io.dico.parcels2.ParcelData
|
||||
import io.dico.parcels2.ParcelOwner
|
||||
import kotlinx.coroutines.experimental.channels.ProducerScope
|
||||
import java.util.*
|
||||
import io.dico.parcels2.*
|
||||
import kotlinx.coroutines.experimental.channels.SendChannel
|
||||
import java.util.UUID
|
||||
|
||||
interface Backing {
|
||||
|
||||
@@ -22,31 +19,31 @@ interface Backing {
|
||||
* This producer function is capable of constantly reading parcels from a potentially infinite sequence,
|
||||
* and provide parcel data for it as read from the database.
|
||||
*/
|
||||
suspend fun ProducerScope<Pair<Parcel, ParcelData?>>.produceParcelData(parcels: Sequence<Parcel>)
|
||||
suspend fun produceParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>)
|
||||
|
||||
suspend fun ProducerScope<Pair<SerializableParcel, ParcelData?>>.produceAllParcelData()
|
||||
suspend fun produceAllParcelData(channel: SendChannel<DataPair>)
|
||||
|
||||
suspend fun readParcelData(parcelFor: Parcel): ParcelData?
|
||||
suspend fun readParcelData(parcel: ParcelId): ParcelData?
|
||||
|
||||
suspend fun getOwnedParcels(user: ParcelOwner): List<SerializableParcel>
|
||||
suspend fun getOwnedParcels(user: ParcelOwner): List<ParcelId>
|
||||
|
||||
suspend fun getNumParcels(user: ParcelOwner): Int = getOwnedParcels(user).size
|
||||
|
||||
|
||||
suspend fun setParcelData(parcelFor: Parcel, data: ParcelData?)
|
||||
suspend fun setParcelData(parcel: ParcelId, data: ParcelData?)
|
||||
|
||||
suspend fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?)
|
||||
suspend fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?)
|
||||
|
||||
suspend fun setLocalPlayerStatus(parcelFor: Parcel, player: UUID, status: AddedStatus)
|
||||
suspend fun setLocalPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus)
|
||||
|
||||
suspend fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean)
|
||||
suspend fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean)
|
||||
|
||||
suspend fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean)
|
||||
suspend fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean)
|
||||
|
||||
|
||||
suspend fun ProducerScope<Pair<ParcelOwner, MutableMap<UUID, AddedStatus>>>.produceAllGlobalAddedData()
|
||||
suspend fun produceAllGlobalAddedData(channel: SendChannel<AddedDataPair<ParcelOwner>>)
|
||||
|
||||
suspend fun readGlobalAddedData(owner: ParcelOwner): MutableMap<UUID, AddedStatus>
|
||||
suspend fun readGlobalAddedData(owner: ParcelOwner): MutableAddedDataMap
|
||||
|
||||
suspend fun setGlobalPlayerStatus(owner: ParcelOwner, player: UUID, status: AddedStatus)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.fasterxml.jackson.databind.ser.BeanSerializerModifier
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
|
||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||
import io.dico.parcels2.GeneratorFactory
|
||||
import io.dico.parcels2.GeneratorFactories
|
||||
import io.dico.parcels2.GeneratorOptions
|
||||
import io.dico.parcels2.StorageOptions
|
||||
import org.bukkit.Bukkit
|
||||
@@ -100,7 +100,7 @@ class GeneratorOptionsDeserializer : JsonDeserializer<GeneratorOptions>() {
|
||||
val node = parser!!.readValueAsTree<JsonNode>()
|
||||
val name = node.get("name").asText()
|
||||
val optionsNode = node.get("options")
|
||||
val factory = GeneratorFactory.getFactory(name) ?: throw IllegalStateException("Unknown generator: $name")
|
||||
val factory = GeneratorFactories.getFactory(name) ?: throw IllegalStateException("Unknown generator: $name")
|
||||
|
||||
return parser.codec.treeToValue(optionsNode, factory.optionsClass.java)
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package io.dico.parcels2.storage
|
||||
|
||||
import io.dico.parcels2.Parcel
|
||||
import io.dico.parcels2.ParcelWorld
|
||||
import io.dico.parcels2.Worlds
|
||||
import io.dico.parcels2.util.Vec2i
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.World
|
||||
import java.util.*
|
||||
|
||||
data class SerializableWorld(val name: String? = null,
|
||||
val uid: UUID? = null) {
|
||||
|
||||
init {
|
||||
uid ?: name ?: throw IllegalArgumentException("uuid and/or name must be present")
|
||||
}
|
||||
|
||||
val world: World? by lazy { uid?.let { Bukkit.getWorld(it) } ?: name?.let { Bukkit.getWorld(it) } }
|
||||
//val parcelWorld: ParcelWorld? by lazy { TODO() }
|
||||
|
||||
constructor(world: World) : this(world.name, world.uid)
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by storage backing options to encompass the location of a parcel
|
||||
*/
|
||||
data class SerializableParcel(val world: SerializableWorld,
|
||||
val pos: Vec2i) {
|
||||
|
||||
//val parcel: Parcel? by lazy { TODO() }
|
||||
}
|
||||
|
||||
fun Worlds.getWorldBySerializedValue(input: SerializableWorld): ParcelWorld? {
|
||||
return input.world?.let { getWorld(it) }
|
||||
}
|
||||
|
||||
fun Worlds.getParcelBySerializedValue(input: SerializableParcel): Parcel? {
|
||||
return getWorldBySerializedValue(input.world)
|
||||
?.parcelByID(input.pos)
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
|
||||
package io.dico.parcels2.storage
|
||||
|
||||
import io.dico.parcels2.AddedStatus
|
||||
import io.dico.parcels2.Parcel
|
||||
import io.dico.parcels2.ParcelData
|
||||
import io.dico.parcels2.ParcelOwner
|
||||
import io.dico.parcels2.*
|
||||
import kotlinx.coroutines.experimental.*
|
||||
import kotlinx.coroutines.experimental.channels.ProducerScope
|
||||
import kotlinx.coroutines.experimental.channels.ReceiveChannel
|
||||
import kotlinx.coroutines.experimental.channels.produce
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
typealias DataPair = Pair<ParcelId, ParcelData?>
|
||||
typealias AddedDataPair<TAttach> = Pair<TAttach, MutableAddedDataMap>
|
||||
|
||||
interface Storage {
|
||||
val name: String
|
||||
val syncDispatcher: CoroutineDispatcher
|
||||
@@ -22,31 +25,31 @@ interface Storage {
|
||||
fun shutdown(): Job
|
||||
|
||||
|
||||
fun readParcelData(parcelFor: Parcel): Deferred<ParcelData?>
|
||||
fun readParcelData(parcel: ParcelId): Deferred<ParcelData?>
|
||||
|
||||
fun readParcelData(parcelsFor: Sequence<Parcel>): ReceiveChannel<Pair<Parcel, ParcelData?>>
|
||||
fun readParcelData(parcels: Sequence<ParcelId>): ReceiveChannel<DataPair>
|
||||
|
||||
fun readAllParcelData(): ReceiveChannel<Pair<SerializableParcel, ParcelData?>>
|
||||
fun readAllParcelData(): ReceiveChannel<DataPair>
|
||||
|
||||
fun getOwnedParcels(user: ParcelOwner): Deferred<List<SerializableParcel>>
|
||||
fun getOwnedParcels(user: ParcelOwner): Deferred<List<ParcelId>>
|
||||
|
||||
fun getNumParcels(user: ParcelOwner): Deferred<Int>
|
||||
|
||||
|
||||
fun setParcelData(parcelFor: Parcel, data: ParcelData?): Job
|
||||
fun setParcelData(parcel: ParcelId, data: ParcelData?): Job
|
||||
|
||||
fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?): Job
|
||||
fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?): Job
|
||||
|
||||
fun setParcelPlayerStatus(parcelFor: Parcel, player: UUID, status: AddedStatus): Job
|
||||
fun setParcelPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus): Job
|
||||
|
||||
fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean): Job
|
||||
fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean): Job
|
||||
|
||||
fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean): Job
|
||||
fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean): Job
|
||||
|
||||
|
||||
fun readAllGlobalAddedData(): ReceiveChannel<Pair<ParcelOwner, MutableMap<UUID, AddedStatus>>>
|
||||
fun readAllGlobalAddedData(): ReceiveChannel<AddedDataPair<ParcelOwner>>
|
||||
|
||||
fun readGlobalAddedData(owner: ParcelOwner): Deferred<MutableMap<UUID, AddedStatus>?>
|
||||
fun readGlobalAddedData(owner: ParcelOwner): Deferred<MutableAddedDataMap?>
|
||||
|
||||
fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus): Job
|
||||
}
|
||||
@@ -59,48 +62,47 @@ class StorageWithCoroutineBacking internal constructor(val backing: Backing) : S
|
||||
override val isConnected get() = backing.isConnected
|
||||
val channelCapacity = 16
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
private inline fun <T> defer(noinline block: suspend CoroutineScope.() -> T): Deferred<T> {
|
||||
return async(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block)
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
private inline fun job(noinline block: suspend CoroutineScope.() -> Unit): Job {
|
||||
return launch(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block)
|
||||
}
|
||||
|
||||
private inline fun <T> openChannel(noinline block: suspend ProducerScope<T>.() -> Unit): ReceiveChannel<T> {
|
||||
return produce(asyncDispatcher, capacity = channelCapacity, block = block)
|
||||
}
|
||||
|
||||
override fun init() = job { backing.init() }
|
||||
|
||||
override fun shutdown() = job { backing.shutdown() }
|
||||
|
||||
|
||||
override fun readParcelData(parcelFor: Parcel) = defer { backing.readParcelData(parcelFor) }
|
||||
override fun readParcelData(parcel: ParcelId) = defer { backing.readParcelData(parcel) }
|
||||
|
||||
override fun readParcelData(parcelsFor: Sequence<Parcel>) =
|
||||
produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceParcelData(parcelsFor) } }
|
||||
override fun readParcelData(parcels: Sequence<ParcelId>) = openChannel<DataPair> { backing.produceParcelData(channel, parcels) }
|
||||
|
||||
override fun readAllParcelData(): ReceiveChannel<Pair<SerializableParcel, ParcelData?>> =
|
||||
produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceAllParcelData() } }
|
||||
override fun readAllParcelData() = openChannel<DataPair> { backing.produceAllParcelData(channel) }
|
||||
|
||||
override fun getOwnedParcels(user: ParcelOwner) = defer { backing.getOwnedParcels(user) }
|
||||
|
||||
override fun getNumParcels(user: ParcelOwner) = defer { backing.getNumParcels(user) }
|
||||
|
||||
override fun setParcelData(parcelFor: Parcel, data: ParcelData?) = job { backing.setParcelData(parcelFor, data) }
|
||||
override fun setParcelData(parcel: ParcelId, data: ParcelData?) = job { backing.setParcelData(parcel, data) }
|
||||
|
||||
override fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = job { backing.setParcelOwner(parcelFor, owner) }
|
||||
override fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?) = job { backing.setParcelOwner(parcel, owner) }
|
||||
|
||||
override fun setParcelPlayerStatus(parcelFor: Parcel, player: UUID, status: AddedStatus) = job { backing.setLocalPlayerStatus(parcelFor, player, status) }
|
||||
override fun setParcelPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus) = job { backing.setLocalPlayerStatus(parcel, player, status) }
|
||||
|
||||
override fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean) = job { backing.setParcelAllowsInteractInventory(parcel, value) }
|
||||
override fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) = job { backing.setParcelAllowsInteractInventory(parcel, value) }
|
||||
|
||||
override fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) = job { backing.setParcelAllowsInteractInputs(parcel, value) }
|
||||
override fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) = job { backing.setParcelAllowsInteractInputs(parcel, value) }
|
||||
|
||||
|
||||
override fun readAllGlobalAddedData(): ReceiveChannel<Pair<ParcelOwner, MutableMap<UUID, AddedStatus>>> =
|
||||
produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceAllGlobalAddedData() } }
|
||||
override fun readAllGlobalAddedData(): ReceiveChannel<AddedDataPair<ParcelOwner>> = openChannel { backing.produceAllGlobalAddedData(channel) }
|
||||
|
||||
override fun readGlobalAddedData(owner: ParcelOwner): Deferred<MutableMap<UUID, AddedStatus>?> = defer { backing.readGlobalAddedData(owner) }
|
||||
override fun readGlobalAddedData(owner: ParcelOwner): Deferred<MutableAddedDataMap?> = defer { backing.readGlobalAddedData(owner) }
|
||||
|
||||
override fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) = job { backing.setGlobalPlayerStatus(owner, player, status) }
|
||||
}
|
||||
|
||||
@@ -40,4 +40,4 @@ class ConnectionStorageFactory : StorageFactory {
|
||||
return StorageWithCoroutineBacking(ExposedBacking(dataSourceFactory))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,18 +5,18 @@ package io.dico.parcels2.storage.exposed
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
import io.dico.parcels2.*
|
||||
import io.dico.parcels2.storage.Backing
|
||||
import io.dico.parcels2.storage.SerializableParcel
|
||||
import io.dico.parcels2.storage.DataPair
|
||||
import io.dico.parcels2.util.toUUID
|
||||
import kotlinx.coroutines.experimental.CoroutineStart
|
||||
import kotlinx.coroutines.experimental.Unconfined
|
||||
import kotlinx.coroutines.experimental.channels.ProducerScope
|
||||
import kotlinx.coroutines.experimental.channels.SendChannel
|
||||
import kotlinx.coroutines.experimental.launch
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SchemaUtils.create
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.jetbrains.exposed.sql.vendors.DatabaseDialect
|
||||
import org.joda.time.DateTime
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
import javax.sql.DataSource
|
||||
|
||||
class ExposedDatabaseException(message: String? = null) : Exception(message)
|
||||
@@ -63,7 +63,7 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) :
|
||||
isShutdown = true
|
||||
}
|
||||
|
||||
override suspend fun ProducerScope<Pair<Parcel, ParcelData?>>.produceParcelData(parcels: Sequence<Parcel>) {
|
||||
override suspend fun produceParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>) {
|
||||
for (parcel in parcels) {
|
||||
val data = readParcelData(parcel)
|
||||
channel.send(parcel to data)
|
||||
@@ -71,32 +71,32 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) :
|
||||
channel.close()
|
||||
}
|
||||
|
||||
override suspend fun ProducerScope<Pair<SerializableParcel, ParcelData?>>.produceAllParcelData() = transactionLaunch {
|
||||
override suspend fun produceAllParcelData(channel: SendChannel<Pair<ParcelId, ParcelData?>>) = transactionLaunch {
|
||||
ParcelsT.selectAll().forEach { row ->
|
||||
val parcel = ParcelsT.getSerializable(row) ?: return@forEach
|
||||
val parcel = ParcelsT.getId(row) ?: return@forEach
|
||||
val data = rowToParcelData(row)
|
||||
channel.send(parcel to data)
|
||||
}
|
||||
channel.close()
|
||||
}
|
||||
|
||||
override suspend fun readParcelData(parcelFor: Parcel): ParcelData? = transaction {
|
||||
val row = ParcelsT.getRow(parcelFor) ?: return@transaction null
|
||||
override suspend fun readParcelData(parcel: ParcelId): ParcelData? = transaction {
|
||||
val row = ParcelsT.getRow(parcel) ?: return@transaction null
|
||||
rowToParcelData(row)
|
||||
}
|
||||
|
||||
override suspend fun getOwnedParcels(user: ParcelOwner): List<SerializableParcel> = transaction {
|
||||
override suspend fun getOwnedParcels(user: ParcelOwner): List<ParcelId> = transaction {
|
||||
val user_id = OwnersT.getId(user) ?: return@transaction emptyList()
|
||||
ParcelsT.select { ParcelsT.owner_id eq user_id }
|
||||
.orderBy(ParcelsT.claim_time, isAsc = true)
|
||||
.mapNotNull(ParcelsT::getSerializable)
|
||||
.mapNotNull(ParcelsT::getId)
|
||||
.toList()
|
||||
}
|
||||
|
||||
override suspend fun setParcelData(parcelFor: Parcel, data: ParcelData?) {
|
||||
override suspend fun setParcelData(parcel: ParcelId, data: ParcelData?) {
|
||||
if (data == null) {
|
||||
transaction {
|
||||
ParcelsT.getId(parcelFor)?.let { id ->
|
||||
ParcelsT.getId(parcel)?.let { id ->
|
||||
ParcelsT.deleteIgnoreWhere { ParcelsT.id eq id }
|
||||
|
||||
// Below should cascade automatically
|
||||
@@ -111,25 +111,25 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) :
|
||||
}
|
||||
|
||||
transaction {
|
||||
val id = ParcelsT.getOrInitId(parcelFor)
|
||||
val id = ParcelsT.getOrInitId(parcel)
|
||||
AddedLocalT.deleteIgnoreWhere { AddedLocalT.attach_id eq id }
|
||||
}
|
||||
|
||||
setParcelOwner(parcelFor, data.owner)
|
||||
setParcelOwner(parcel, data.owner)
|
||||
|
||||
for ((uuid, status) in data.addedMap) {
|
||||
setLocalPlayerStatus(parcelFor, uuid, status)
|
||||
setLocalPlayerStatus(parcel, uuid, status)
|
||||
}
|
||||
|
||||
setParcelAllowsInteractInputs(parcelFor, data.allowInteractInputs)
|
||||
setParcelAllowsInteractInventory(parcelFor, data.allowInteractInventory)
|
||||
setParcelAllowsInteractInputs(parcel, data.allowInteractInputs)
|
||||
setParcelAllowsInteractInventory(parcel, data.allowInteractInventory)
|
||||
}
|
||||
|
||||
override suspend fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = transaction {
|
||||
override suspend fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?) = transaction {
|
||||
val id = if (owner == null)
|
||||
ParcelsT.getId(parcelFor) ?: return@transaction
|
||||
ParcelsT.getId(parcel) ?: return@transaction
|
||||
else
|
||||
ParcelsT.getOrInitId(parcelFor)
|
||||
ParcelsT.getOrInitId(parcel)
|
||||
|
||||
val owner_id = owner?.let { OwnersT.getOrInitId(it) }
|
||||
val time = owner?.let { DateTime.now() }
|
||||
@@ -140,11 +140,11 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) :
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setLocalPlayerStatus(parcelFor: Parcel, player: UUID, status: AddedStatus) = transaction {
|
||||
AddedLocalT.setPlayerStatus(parcelFor, player, status)
|
||||
override suspend fun setLocalPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus) = transaction {
|
||||
AddedLocalT.setPlayerStatus(parcel, player, status)
|
||||
}
|
||||
|
||||
override suspend fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean): Unit = transaction {
|
||||
override suspend fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean): Unit = transaction {
|
||||
val id = ParcelsT.getOrInitId(parcel)
|
||||
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
|
||||
it[ParcelOptionsT.parcel_id] = id
|
||||
@@ -152,7 +152,7 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) :
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean): Unit = transaction {
|
||||
override suspend fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean): Unit = transaction {
|
||||
val id = ParcelsT.getOrInitId(parcel)
|
||||
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
|
||||
it[ParcelOptionsT.parcel_id] = id
|
||||
@@ -160,7 +160,7 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) :
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun ProducerScope<Pair<ParcelOwner, MutableMap<UUID, AddedStatus>>>.produceAllGlobalAddedData() = transactionLaunch {
|
||||
override suspend fun produceAllGlobalAddedData(channel: SendChannel<Pair<ParcelOwner, MutableMap<UUID, AddedStatus>>>) = transactionLaunch {
|
||||
AddedGlobalT.sendAllAddedData(channel)
|
||||
channel.close()
|
||||
}
|
||||
@@ -174,7 +174,7 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) :
|
||||
}
|
||||
|
||||
private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply {
|
||||
owner = row[ParcelsT.owner_id]?.let { OwnersT.getSerializable(it) }
|
||||
owner = row[ParcelsT.owner_id]?.let { OwnersT.getId(it) }
|
||||
since = row[ParcelsT.claim_time]
|
||||
|
||||
val parcelId = row[ParcelsT.id]
|
||||
|
||||
@@ -40,7 +40,7 @@ class UpsertStatement<Key : Any>(table: Table, conflictColumn: Column<*>? = null
|
||||
|
||||
} else {
|
||||
|
||||
append (" ON DUPLICATE KEY UPDATE ")
|
||||
append(" ON DUPLICATE KEY UPDATE ")
|
||||
values.keys.filter { it !in indexColumns }.joinTo(this) { "${transaction.identity(it)}=VALUES(${transaction.identity(it)})" }
|
||||
|
||||
}
|
||||
|
||||
@@ -2,20 +2,16 @@
|
||||
|
||||
package io.dico.parcels2.storage.exposed
|
||||
|
||||
import io.dico.parcels2.Parcel
|
||||
import io.dico.parcels2.ParcelId
|
||||
import io.dico.parcels2.ParcelOwner
|
||||
import io.dico.parcels2.ParcelWorld
|
||||
import io.dico.parcels2.storage.SerializableParcel
|
||||
import io.dico.parcels2.storage.SerializableWorld
|
||||
import io.dico.parcels2.util.Vec2i
|
||||
import io.dico.parcels2.ParcelWorldId
|
||||
import io.dico.parcels2.util.toByteArray
|
||||
import io.dico.parcels2.util.toUUID
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
|
||||
sealed class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj, SerializableObj>,
|
||||
QueryObj, SerializableObj>(tableName: String, columnName: String)
|
||||
sealed class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj>, QueryObj>(tableName: String, columnName: String)
|
||||
: Table(tableName) {
|
||||
val id = integer(columnName).autoIncrement().primaryKey()
|
||||
|
||||
@@ -35,31 +31,32 @@ sealed class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj,
|
||||
|
||||
abstract fun getId(obj: QueryObj): Int?
|
||||
abstract fun getOrInitId(obj: QueryObj): Int
|
||||
fun getSerializable(id: Int): SerializableObj? = select { this@IdTransactionsTable.id eq id }.firstOrNull()?.let { getSerializable(it) }
|
||||
abstract fun getSerializable(row: ResultRow): SerializableObj?
|
||||
fun getId(id: Int): QueryObj? = select { this@IdTransactionsTable.id eq id }.firstOrNull()?.let { getId(it) }
|
||||
abstract fun getId(row: ResultRow): QueryObj?
|
||||
}
|
||||
|
||||
object WorldsT : IdTransactionsTable<WorldsT, ParcelWorld, SerializableWorld>("parcel_worlds", "world_id") {
|
||||
object WorldsT : IdTransactionsTable<WorldsT, ParcelWorldId>("parcel_worlds", "world_id") {
|
||||
val name = varchar("name", 50)
|
||||
val uid = binary("uid", 16)
|
||||
val uid = binary("uid", 16).nullable()
|
||||
val index_name = uniqueIndexR("index_name", name)
|
||||
val index_uid = uniqueIndexR("index_uid", uid)
|
||||
|
||||
internal inline fun getId(binaryUid: ByteArray): Int? = getId { uid eq binaryUid }
|
||||
internal inline fun getId(uid: UUID): Int? = getId(uid.toByteArray())
|
||||
internal inline fun getOrInitId(worldUid: UUID, worldName: String): Int = worldUid.toByteArray().let { binaryUid ->
|
||||
getId(binaryUid)
|
||||
?: insertAndGetId("world named $worldName") { it[uid] = binaryUid; it[name] = worldName }
|
||||
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 ->
|
||||
getId(worldName, binaryUid)
|
||||
?: insertAndGetId("world named $worldName") { it[name] = worldName; binaryUid?.let { buid -> it[uid] = buid } }
|
||||
}
|
||||
|
||||
override fun getId(world: ParcelWorld): Int? = getId(world.world.uid)
|
||||
override fun getOrInitId(world: ParcelWorld): Int = world.world.let { getOrInitId(it.uid, it.name) }
|
||||
override fun getId(world: ParcelWorldId): Int? = getId(world.name, world.uid)
|
||||
override fun getOrInitId(world: ParcelWorldId): Int = getOrInitId(world.name, world.uid)
|
||||
|
||||
override fun getSerializable(row: ResultRow): SerializableWorld {
|
||||
return SerializableWorld(row[name], row[uid].toUUID())
|
||||
override fun getId(row: ResultRow): ParcelWorldId {
|
||||
return ParcelWorldId(row[name], row[uid]?.toUUID())
|
||||
}
|
||||
}
|
||||
|
||||
object ParcelsT : IdTransactionsTable<ParcelsT, Parcel, SerializableParcel>("parcels", "parcel_id") {
|
||||
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")
|
||||
@@ -68,27 +65,27 @@ object ParcelsT : IdTransactionsTable<ParcelsT, Parcel, SerializableParcel>("par
|
||||
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(worldUid: UUID, parcelX: Int, parcelZ: Int): Int? = WorldsT.getId(worldUid)?.let { getId(it, parcelX, parcelZ) }
|
||||
private inline fun getOrInitId(worldUid: UUID, worldName: String, parcelX: Int, parcelZ: Int): Int {
|
||||
val worldId = WorldsT.getOrInitId(worldUid, worldName)
|
||||
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 getId(worldId, parcelX, parcelZ)
|
||||
?: insertAndGetId("parcel at $worldName($parcelX, $parcelZ)") { it[world_id] = worldId; it[px] = parcelX; it[pz] = parcelZ }
|
||||
}
|
||||
|
||||
override fun getId(parcel: Parcel): Int? = getId(parcel.world.world.uid, parcel.pos.x, parcel.pos.z)
|
||||
override fun getOrInitId(parcel: Parcel): Int = parcel.world.world.let { getOrInitId(it.uid, it.name, parcel.pos.x, parcel.pos.z) }
|
||||
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: Parcel): ResultRow? = getId(parcel)?.let { getRow(it) }
|
||||
fun getRow(parcel: ParcelId): ResultRow? = getId(parcel)?.let { getRow(it) }
|
||||
|
||||
override fun getSerializable(row: ResultRow): SerializableParcel? {
|
||||
override fun getId(row: ResultRow): ParcelId? {
|
||||
val worldId = row[world_id]
|
||||
val world = WorldsT.getSerializable(worldId) ?: return null
|
||||
return SerializableParcel(world, Vec2i(row[px], row[pz]))
|
||||
val world = WorldsT.getId(worldId) ?: return null
|
||||
return ParcelId(world, row[px], row[pz])
|
||||
}
|
||||
}
|
||||
|
||||
object OwnersT : IdTransactionsTable<OwnersT, ParcelOwner, ParcelOwner>("parcel_owners", "owner_id") {
|
||||
object OwnersT : IdTransactionsTable<OwnersT, ParcelOwner>("parcel_owners", "owner_id") {
|
||||
val uuid = binary("uuid", 16).nullable()
|
||||
val name = varchar("name", 32)
|
||||
val index_pair = uniqueIndexR("index_pair", uuid, name)
|
||||
@@ -115,7 +112,7 @@ object OwnersT : IdTransactionsTable<OwnersT, ParcelOwner, ParcelOwner>("parcel_
|
||||
if (owner.hasUUID) getOrInitId(owner.uuid!!, owner.notNullName)
|
||||
else getOrInitId(owner.name!!)
|
||||
|
||||
override fun getSerializable(row: ResultRow): ParcelOwner {
|
||||
override fun getId(row: ResultRow): ParcelOwner {
|
||||
return row[uuid]?.toUUID()?.let { ParcelOwner(it) } ?: ParcelOwner(row[name])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,17 +3,16 @@
|
||||
package io.dico.parcels2.storage.exposed
|
||||
|
||||
import io.dico.parcels2.AddedStatus
|
||||
import io.dico.parcels2.Parcel
|
||||
import io.dico.parcels2.ParcelId
|
||||
import io.dico.parcels2.ParcelOwner
|
||||
import io.dico.parcels2.storage.SerializableParcel
|
||||
import io.dico.parcels2.util.toByteArray
|
||||
import io.dico.parcels2.util.toUUID
|
||||
import kotlinx.coroutines.experimental.channels.SendChannel
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
|
||||
object AddedLocalT : AddedTable<Parcel, SerializableParcel>("parcels_added_local", ParcelsT)
|
||||
object AddedGlobalT : AddedTable<ParcelOwner, ParcelOwner>("parcels_added_global", OwnersT)
|
||||
object AddedLocalT : AddedTable<ParcelId>("parcels_added_local", ParcelsT)
|
||||
object AddedGlobalT : AddedTable<ParcelOwner>("parcels_added_global", OwnersT)
|
||||
|
||||
object ParcelOptionsT : Table("parcel_options") {
|
||||
val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE)
|
||||
@@ -23,7 +22,7 @@ object ParcelOptionsT : Table("parcel_options") {
|
||||
|
||||
typealias AddedStatusSendChannel<AttachT> = SendChannel<Pair<AttachT, MutableMap<UUID, AddedStatus>>>
|
||||
|
||||
sealed class AddedTable<AttachT, SerializableT>(name: String, val idTable: IdTransactionsTable<*, AttachT, SerializableT>) : Table(name) {
|
||||
sealed class AddedTable<AttachT>(name: String, val idTable: IdTransactionsTable<*, AttachT>) : Table(name) {
|
||||
val attach_id = integer("attach_id").references(idTable.id, ReferenceOption.CASCADE)
|
||||
val player_uuid = binary("player_uuid", 16)
|
||||
val allowed_flag = bool("allowed_flag")
|
||||
@@ -52,7 +51,7 @@ sealed class AddedTable<AttachT, SerializableT>(name: String, val idTable: IdTra
|
||||
.associateByTo(hashMapOf(), { it[player_uuid].toUUID() }, { it[allowed_flag].asAddedStatus() })
|
||||
}
|
||||
|
||||
suspend fun sendAllAddedData(channel: AddedStatusSendChannel<SerializableT>) {
|
||||
suspend fun sendAllAddedData(channel: AddedStatusSendChannel<AttachT>) {
|
||||
/*
|
||||
val iterator = selectAll().orderBy(attach_id).iterator()
|
||||
|
||||
@@ -63,7 +62,7 @@ sealed class AddedTable<AttachT, SerializableT>(name: String, val idTable: IdTra
|
||||
var map: MutableMap<UUID, AddedStatus>? = null
|
||||
|
||||
fun initAttachAndMap() {
|
||||
attach = idTable.getSerializable(id)
|
||||
attach = idTable.getId(id)
|
||||
map = attach?.let { mutableMapOf() }
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package io.dico.parcels2.storage.migration
|
||||
|
||||
import io.dico.parcels2.storage.Storage
|
||||
|
||||
interface Migration {
|
||||
fun migrateTo(storage: Storage)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package io.dico.parcels2.storage.migration
|
||||
|
||||
interface MigrationFactory {
|
||||
fun getMigration()
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package io.dico.parcels2.storage.migration.plotme
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
import io.dico.parcels2.*
|
||||
import io.dico.parcels2.storage.Storage
|
||||
import io.dico.parcels2.storage.migration.Migration
|
||||
import io.dico.parcels2.util.Vec2i
|
||||
import io.dico.parcels2.util.isValid
|
||||
import io.dico.parcels2.util.toUUID
|
||||
import io.dico.parcels2.util.uuid
|
||||
import kotlinx.coroutines.experimental.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.experimental.launch
|
||||
import org.bukkit.Bukkit
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.sql.Blob
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.Executors
|
||||
import javax.sql.DataSource
|
||||
|
||||
class PlotmeMigration(val parcelProvider: ParcelProvider,
|
||||
val worldMapper: Map<String, String>,
|
||||
val dataSourceFactory: () -> DataSource) : Migration {
|
||||
private var dataSource: DataSource? = null
|
||||
private var database: Database? = null
|
||||
private var isShutdown: Boolean = false
|
||||
private val dispatcher = Executors.newSingleThreadExecutor { Thread(it, "PlotMe Migration Thread") }.asCoroutineDispatcher()
|
||||
private val mlogger = LoggerFactory.getLogger("PlotMe Migrator")
|
||||
|
||||
private fun <T> transaction(statement: Transaction.() -> T) = org.jetbrains.exposed.sql.transactions.transaction(database!!, statement)
|
||||
|
||||
override fun migrateTo(storage: Storage) {
|
||||
launch(context = dispatcher) {
|
||||
init()
|
||||
doWork(storage)
|
||||
shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
fun init() {
|
||||
if (isShutdown) throw IllegalStateException()
|
||||
dataSource = dataSourceFactory()
|
||||
database = Database.connect(dataSource!!)
|
||||
}
|
||||
|
||||
fun shutdown() {
|
||||
if (isShutdown) throw IllegalStateException()
|
||||
dataSource?.let {
|
||||
(it as? HikariDataSource)?.close()
|
||||
}
|
||||
database = null
|
||||
isShutdown = true
|
||||
}
|
||||
|
||||
val parcelsCache = hashMapOf<String, MutableMap<Vec2i, ParcelData>>()
|
||||
|
||||
private fun getMap(worldName: String): MutableMap<Vec2i, ParcelData>? {
|
||||
val mapped = worldMapper[worldName] ?: return null
|
||||
return parcelsCache.computeIfAbsent(mapped) { mutableMapOf() }
|
||||
}
|
||||
|
||||
private fun getData(worldName: String, position: Vec2i): ParcelData? {
|
||||
return getMap(worldName)?.computeIfAbsent(position) { ParcelDataHolder() }
|
||||
}
|
||||
|
||||
fun doWork(target: Storage): Unit = transaction {
|
||||
if (!PlotmePlotsT.exists()) {
|
||||
mlogger.warn("Plotme tables don't appear to exist. Exiting.")
|
||||
return@transaction
|
||||
}
|
||||
parcelsCache.clear()
|
||||
|
||||
iterPlotmeTable(PlotmePlotsT) { data, row ->
|
||||
// in practice, owner_uuid is not null for any plot currently. It will convert well.
|
||||
data.owner = ParcelOwner(row[owner_uuid]?.toUUID(), row[owner_name])
|
||||
}
|
||||
|
||||
iterPlotmeTable(PlotmeAllowedT) { data, row ->
|
||||
val uuid = row[player_uuid]?.toUUID()
|
||||
?: Bukkit.getOfflinePlayer(row[player_name]).takeIf { it.isValid }?.uuid
|
||||
?: return@iterPlotmeTable
|
||||
|
||||
data.setAddedStatus(uuid, AddedStatus.ALLOWED)
|
||||
}
|
||||
|
||||
iterPlotmeTable(PlotmeDeniedT) { data, row ->
|
||||
val uuid = row[PlotmeAllowedT.player_uuid]?.toUUID()
|
||||
?: Bukkit.getOfflinePlayer(row[PlotmeAllowedT.player_name]).takeIf { it.isValid }?.uuid
|
||||
?: return@iterPlotmeTable
|
||||
|
||||
data.setAddedStatus(uuid, AddedStatus.BANNED)
|
||||
}
|
||||
|
||||
for ((worldName, map) in parcelsCache) {
|
||||
val world = ParcelWorldId(worldName)
|
||||
for ((pos, data) in map) {
|
||||
val parcel = ParcelId(world, pos)
|
||||
target.setParcelData(parcel, data)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun Blob.toUUID(): UUID {
|
||||
val out = ByteArrayOutputStream(16)
|
||||
binaryStream.copyTo(out, bufferSize = 16)
|
||||
return out.toByteArray().toUUID()
|
||||
}
|
||||
|
||||
private inline fun <T : PlotmeTable> iterPlotmeTable(table: T, block: T.(ParcelData, ResultRow) -> Unit) {
|
||||
table.selectAll().forEach { row ->
|
||||
val data = getData(row[table.world_name], Vec2i(row[table.px], row[table.pz])) ?: return@forEach
|
||||
table.block(data, row)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.dico.parcels2.storage.migration.plotme
|
||||
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
|
||||
const val uppercase: Boolean = false
|
||||
@Suppress("ConstantConditionIf")
|
||||
fun String.toCorrectCase() = if (uppercase) this else toLowerCase()
|
||||
|
||||
sealed class PlotmeTable(name: String) : Table(name) {
|
||||
val px = PlotmePlotsT.integer("idX").primaryKey()
|
||||
val pz = PlotmePlotsT.integer("idZ").primaryKey()
|
||||
val world_name = PlotmePlotsT.varchar("world", 32).primaryKey()
|
||||
}
|
||||
|
||||
object PlotmePlotsT : PlotmeTable("plotmePlots".toCorrectCase()) {
|
||||
val owner_name = varchar("owner", 32)
|
||||
val owner_uuid = blob("ownerid").nullable()
|
||||
}
|
||||
|
||||
sealed class PlotmePlotPlayerMap(name: String) : PlotmeTable(name) {
|
||||
val player_name = PlotmePlotsT.varchar("player", 32)
|
||||
val player_uuid = PlotmePlotsT.blob("playerid").nullable()
|
||||
}
|
||||
|
||||
object PlotmeAllowedT : PlotmePlotPlayerMap("plotmeAllowed".toCorrectCase())
|
||||
object PlotmeDeniedT : PlotmePlotPlayerMap("plotmeDenied".toCorrectCase())
|
||||
Reference in New Issue
Block a user