summaryrefslogtreecommitdiff
path: root/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt')
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt196
1 files changed, 196 insertions, 0 deletions
diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt
new file mode 100644
index 0000000..5685346
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt
@@ -0,0 +1,196 @@
+@file:Suppress("NOTHING_TO_INLINE", "PARAMETER_NAME_CHANGED_ON_OVERRIDE", "LocalVariableName")
+
+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.DataPair
+import io.dico.parcels2.util.toUUID
+import kotlinx.coroutines.experimental.CoroutineStart
+import kotlinx.coroutines.experimental.Unconfined
+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.UUID
+import javax.sql.DataSource
+
+class ExposedDatabaseException(message: String? = null) : Exception(message)
+
+class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : Backing {
+ override val name get() = "Exposed"
+ private var dataSource: DataSource? = null
+ private var database: Database? = null
+ private var isShutdown: Boolean = false
+
+ override val isConnected get() = database != null
+
+ companion object {
+ init {
+ Database.registerDialect("mariadb") {
+ Class.forName("org.jetbrains.exposed.sql.vendors.MysqlDialect").newInstance() as DatabaseDialect
+ }
+ }
+ }
+
+ private fun <T> transaction(statement: Transaction.() -> T) = transaction(database!!, statement)
+
+ private suspend fun transactionLaunch(statement: suspend Transaction.() -> Unit): Unit = transaction(database!!) {
+ launch(context = Unconfined, start = CoroutineStart.UNDISPATCHED) {
+ statement(this@transaction)
+ }
+ }
+
+ override suspend fun init() {
+ if (isShutdown) throw IllegalStateException()
+ dataSource = dataSourceFactory()
+ database = Database.connect(dataSource!!)
+ transaction(database) {
+ create(WorldsT, OwnersT, ParcelsT, ParcelOptionsT, AddedLocalT, AddedGlobalT)
+ }
+ }
+
+ override suspend fun shutdown() {
+ if (isShutdown) throw IllegalStateException()
+ dataSource?.let {
+ (it as? HikariDataSource)?.close()
+ }
+ database = null
+ isShutdown = true
+ }
+
+ override suspend fun produceParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>) {
+ for (parcel in parcels) {
+ val data = readParcelData(parcel)
+ channel.send(parcel to data)
+ }
+ channel.close()
+ }
+
+ override suspend fun produceAllParcelData(channel: SendChannel<Pair<ParcelId, ParcelData?>>) = transactionLaunch {
+ ParcelsT.selectAll().forEach { row ->
+ val parcel = ParcelsT.getId(row) ?: return@forEach
+ val data = rowToParcelData(row)
+ channel.send(parcel to data)
+ }
+ channel.close()
+ }
+
+ 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<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::getId)
+ .toList()
+ }
+
+ override suspend fun setParcelData(parcel: ParcelId, data: ParcelData?) {
+ if (data == null) {
+ transaction {
+ ParcelsT.getId(parcel)?.let { id ->
+ ParcelsT.deleteIgnoreWhere { ParcelsT.id eq id }
+
+ // Below should cascade automatically
+ /*
+ AddedLocalT.deleteIgnoreWhere { AddedLocalT.parcel_id eq id }
+ ParcelOptionsT.deleteIgnoreWhere(limit = 1) { ParcelOptionsT.parcel_id eq id }
+ */
+ }
+
+ }
+ return
+ }
+
+ transaction {
+ val id = ParcelsT.getOrInitId(parcel)
+ AddedLocalT.deleteIgnoreWhere { AddedLocalT.attach_id eq id }
+ }
+
+ setParcelOwner(parcel, data.owner)
+
+ for ((uuid, status) in data.addedMap) {
+ setLocalPlayerStatus(parcel, uuid, status)
+ }
+
+ setParcelAllowsInteractInputs(parcel, data.allowInteractInputs)
+ setParcelAllowsInteractInventory(parcel, data.allowInteractInventory)
+ }
+
+ override suspend fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?) = transaction {
+ val id = if (owner == null)
+ ParcelsT.getId(parcel) ?: return@transaction
+ else
+ ParcelsT.getOrInitId(parcel)
+
+ val owner_id = owner?.let { OwnersT.getOrInitId(it) }
+ val time = owner?.let { DateTime.now() }
+
+ ParcelsT.update({ ParcelsT.id eq id }) {
+ it[ParcelsT.owner_id] = owner_id
+ it[claim_time] = time
+ }
+ }
+
+ override suspend fun setLocalPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus) = transaction {
+ AddedLocalT.setPlayerStatus(parcel, player, status)
+ }
+
+ 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
+ it[ParcelOptionsT.interact_inventory] = value
+ }
+ }
+
+ 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
+ it[ParcelOptionsT.interact_inputs] = value
+ }
+ }
+
+ override suspend fun produceAllGlobalAddedData(channel: SendChannel<Pair<ParcelOwner, MutableMap<UUID, AddedStatus>>>) = transactionLaunch {
+ AddedGlobalT.sendAllAddedData(channel)
+ channel.close()
+ }
+
+ override suspend fun readGlobalAddedData(owner: ParcelOwner): MutableMap<UUID, AddedStatus> = transaction {
+ return@transaction AddedGlobalT.readAddedData(OwnersT.getId(owner) ?: return@transaction hashMapOf())
+ }
+
+ override suspend fun setGlobalPlayerStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) = transaction {
+ AddedGlobalT.setPlayerStatus(owner, player, status)
+ }
+
+ private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply {
+ owner = row[ParcelsT.owner_id]?.let { OwnersT.getId(it) }
+ since = row[ParcelsT.claim_time]
+
+ val parcelId = row[ParcelsT.id]
+ addedMap = AddedLocalT.readAddedData(parcelId)
+
+ AddedLocalT.select { AddedLocalT.attach_id eq parcelId }.forEach {
+ val uuid = it[AddedLocalT.player_uuid].toUUID()
+ val status = if (it[AddedLocalT.allowed_flag]) AddedStatus.ALLOWED else AddedStatus.BANNED
+ setAddedStatus(uuid, status)
+ }
+
+ ParcelOptionsT.select { ParcelOptionsT.parcel_id eq parcelId }.firstOrNull()?.let {
+ allowInteractInputs = it[ParcelOptionsT.interact_inputs]
+ allowInteractInventory = it[ParcelOptionsT.interact_inventory]
+ }
+ }
+
+}
+