Archived
0

Minor tweaks, switch to PostgreSQL for debuggigng purposes

This commit is contained in:
Dico Karssiens
2018-07-26 22:50:29 +01:00
parent bf1da03370
commit ea7c27a7fd
12 changed files with 135 additions and 219 deletions

View File

@@ -17,7 +17,7 @@ import java.util.*
class Options {
var worlds: Map<String, WorldOptions> = HashMap()
private set
var storage: StorageOptions = StorageOptions("mysql", DataConnectionOptions())
var storage: StorageOptions = StorageOptions("postgresql", DataConnectionOptions())
fun addWorld(name: String, options: WorldOptions) = (worlds as MutableMap).put(name, options)
@@ -90,7 +90,7 @@ data class DataConnectionOptions(val address: String = "localhost",
logger.error("(Invalidly) blank address in data storage options")
}
val port = address.substring(idx).toIntOrNull() ?: return null.also {
val port = address.substring(idx + 1).toIntOrNull() ?: return null.also {
logger.error("Invalid port number in data storage options: $it, using $defaultPort as default")
}

View File

@@ -66,7 +66,12 @@ class ParcelsPlugin : JavaPlugin() {
yamlObjectMapper.readerForUpdating(options).readValue<Options>(optionsFile)
} else if (optionsFile.tryCreate()) {
options.addWorld("plotworld", WorldOptions())
yamlObjectMapper.writeValue(optionsFile, options)
try {
yamlObjectMapper.writeValue(optionsFile, options)
} catch (ex: Throwable) {
optionsFile.delete()
throw ex
}
} else {
plogger.error("Failed to save options file ${optionsFile.canonicalPath}")
return false

View File

@@ -1,83 +0,0 @@
package io.dico.parcels2.command
import io.dico.dicore.command.CommandException
import io.dico.dicore.command.CommandResult
import io.dico.dicore.command.EMessageType
import io.dico.dicore.command.ExecutionContext
import io.dico.dicore.command.chat.IChatController
import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.logger
import kotlinx.coroutines.experimental.*
import org.bukkit.command.CommandSender
import kotlin.coroutines.experimental.Continuation
import kotlin.coroutines.experimental.suspendCoroutine
/*
* Interface to implicitly access plugin by creating extension functions for it
*/
interface HasPlugin {
val plugin: ParcelsPlugin
}
class CommandAsyncScope {
suspend fun <T> HasPlugin.awaitSynchronousTask(delay: Int = 0, task: () -> T): T {
return suspendCoroutine { cont: Continuation<T> ->
plugin.server.scheduler.runTaskLater(plugin, l@{
val result = try {
task()
} catch (ex: CommandException) {
cont.resumeWithException(ex)
return@l
} catch (ex: Throwable) {
cont.context.cancel(ex)
return@l
}
cont.resume(result)
}, delay.toLong())
}
}
fun <T> HasPlugin.synchronousTask(delay: Int = 0, task: () -> T): Deferred<T> {
return async { awaitSynchronousTask(delay, task) }
}
}
fun <T : Any?> HasPlugin.delegateCommandAsync(context: ExecutionContext,
block: suspend CommandAsyncScope.() -> T) {
val job: Deferred<Any?> = async(/*context = plugin.storage.asyncDispatcher, */start = CoroutineStart.ATOMIC) {
CommandAsyncScope().block()
}
fun Job.invokeOnCompletionSynchronously(block: (Throwable?) -> Unit) = invokeOnCompletion {
plugin.server.scheduler.runTask(plugin) { block(it) }
}
job.invokeOnCompletionSynchronously l@{ exception: Throwable? ->
exception?.let {
context.address.chatController.handleCoroutineException(context.sender, context, it)
return@l
}
val result = job.getCompleted()
val message = when (result) {
is String -> result
is CommandResult -> result.message
else -> null
}
context.address.chatController.sendMessage(context.sender, EMessageType.RESULT, message)
}
}
fun IChatController.handleCoroutineException(sender: CommandSender, context: ExecutionContext, exception: Throwable) {
if (exception is CancellationException) {
sendMessage(sender, EMessageType.EXCEPTION, "The command was cancelled unexpectedly (see console)")
logger.warn("An asynchronously dispatched command was cancelled unexpectedly", exception)
} else {
handleException(sender, context, exception)
}
}

View File

@@ -19,44 +19,28 @@ import kotlin.reflect.jvm.kotlinFunction
@Retention(AnnotationRetention.RUNTIME)
annotation class ParcelRequire(val admin: Boolean = false, val owner: Boolean = false)
sealed class BaseScope(private var _timeout: Int = 0) : ICommandSuspendReceiver {
override fun getTimeout() = _timeout
fun setTimeout(timeout: Int) {
_timeout = timeout
}
}
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class SuspensionTimeout(val millis: Int)
class SuspendOnlyScope : BaseScope()
class ParcelScope(val world: ParcelWorld, val parcel: Parcel) : BaseScope()
class WorldOnlyScope(val world: ParcelWorld) : BaseScope()
open class WorldScope(val world: ParcelWorld) : ICommandReceiver
open class ParcelScope(val parcel: Parcel) : WorldScope(parcel.world)
fun getParcelCommandReceiver(worlds: Worlds, context: ExecutionContext, method: Method, cmdName: String): ICommandReceiver {
val player = context.sender as Player
val function = method.kotlinFunction!!
val receiverType = function.extensionReceiverParameter!!.type
logger.info("Receiver type: ${receiverType.javaType.typeName}")
val require = function.findAnnotation<ParcelRequire>()
val admin = require?.admin == true
val owner = require?.owner == true
val player = context.sender as Player
return when (receiverType.jvmErasure) {
ParcelScope::class -> worlds.getParcelRequired(player, admin = admin, own = owner).let {
ParcelScope(it.world, it)
}
WorldOnlyScope::class -> worlds.getWorldRequired(player, admin = admin).let {
WorldOnlyScope(it)
}
SuspendOnlyScope::class -> SuspendOnlyScope()
ParcelScope::class -> ParcelScope(worlds.getParcelRequired(player, admin, owner))
WorldScope::class -> WorldScope(worlds.getWorldRequired(player, admin))
else -> throw InternalError("Invalid command receiver type")
}
}
/*
* Functions for checking
*/
fun Worlds.getWorldRequired(player: Player, admin: Boolean = false): ParcelWorld {
if (admin) Validate.isTrue(player.hasAdminManage, "You must have admin rights to use that command")
return getWorld(player.world)

View File

@@ -8,6 +8,7 @@ import io.dico.dicore.command.annotation.Desc
import io.dico.dicore.command.annotation.RequireParameters
import io.dico.parcels2.ParcelOwner
import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.command.NamedParcelDefaultValue.FIRST_OWNED
import io.dico.parcels2.logger
import io.dico.parcels2.storage.getParcelBySerializedValue
import io.dico.parcels2.util.hasParcelHomeOthers
@@ -34,7 +35,7 @@ class ParcelCommands(val plugin: ParcelsPlugin) : ICommandReceiver.Factory {
@Desc("Finds the unclaimed parcel nearest to origin,",
"and gives it to you",
shortVersion = "sets you up with a fresh, unclaimed parcel")
suspend fun WorldOnlyScope.cmdAuto(player: Player): Any? {
suspend fun WorldScope.cmdAuto(player: Player): Any? {
logger.info("cmdAuto thread before await: ${Thread.currentThread().name}")
val numOwnedParcels = plugin.storage.getNumParcels(ParcelOwner(uuid = player.uuid)).await()
logger.info("cmdAuto thread before await: ${Thread.currentThread().name}")
@@ -65,8 +66,9 @@ class ParcelCommands(val plugin: ParcelsPlugin) : ICommandReceiver.Factory {
"more than one parcel",
shortVersion = "teleports you to parcels")
@RequireParameters(0)
suspend fun SuspendOnlyScope.cmdHome(player: Player, context: ExecutionContext,
@NamedParcelDefault(NamedParcelDefaultValue.FIRST_OWNED) target: NamedParcelTarget): Any? {
suspend fun cmdHome(player: Player,
@NamedParcelDefault(FIRST_OWNED) target: NamedParcelTarget): Any?
{
if (player !== target.player && !player.hasParcelHomeOthers) {
error("You do not have permission to teleport to other people's parcels")
}

View File

@@ -57,32 +57,30 @@ object ParcelOptionsT : Table("parcel_options") {
private class ExposedDatabaseException(message: String? = null) : Exception(message)
@Suppress("NOTHING_TO_INLINE")
class ExposedBacking(val dataSource: DataSource) : Backing {
class ExposedBacking(private val dataSourceFactory: () -> 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
override suspend fun init() {
synchronized {
if (isShutdown) throw IllegalStateException()
database = Database.connect(dataSource)
transaction(database) {
create(WorldsT, ParcelsT, AddedLocalT, ParcelOptionsT)
}
if (isShutdown) throw IllegalStateException()
dataSource = dataSourceFactory()
database = Database.connect(dataSource!!)
transaction(database) {
create(WorldsT, ParcelsT, AddedLocalT, ParcelOptionsT)
}
}
override suspend fun shutdown() {
synchronized {
if (isShutdown) throw IllegalStateException()
if (dataSource is HikariDataSource) {
dataSource.close()
}
database = null
isShutdown = true
if (isShutdown) throw IllegalStateException()
dataSource?.let {
if (it is HikariDataSource) it.close()
}
database = null
isShutdown = true
}
private fun <T> transaction(statement: Transaction.() -> T) = transaction(database, statement)
@@ -206,7 +204,7 @@ class ExposedBacking(val dataSource: DataSource) : Backing {
if (data == null) {
transaction {
getParcelId(parcelFor)?.let { id ->
ParcelsT.deleteIgnoreWhere(limit = 1) { ParcelsT.id eq id }
ParcelsT.deleteIgnoreWhere() { ParcelsT.id eq id }
// Below should cascade automatically
/*
@@ -245,7 +243,7 @@ class ExposedBacking(val dataSource: DataSource) : Backing {
else
getOrInitParcelId(parcelFor)
ParcelsT.update({ ParcelsT.id eq id }, limit = 1) {
ParcelsT.update({ ParcelsT.id eq id }) {
it[ParcelsT.owner_uuid] = binaryUuid
it[ParcelsT.owner_name] = name
}

View File

@@ -1,25 +1,34 @@
package io.dico.parcels2.storage
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.DataConnectionOptions
import javax.sql.DataSource
fun getHikariDataSource(dialectName: String,
driver: String,
dco: DataConnectionOptions): DataSource = with(HikariConfig()) {
fun getHikariConfig(dialectName: String,
dco: DataConnectionOptions): HikariConfig = HikariConfig().apply {
val (address, port) = dco.splitAddressAndPort() ?: throw IllegalArgumentException("Invalid address: ${dco.address}")
poolName = "redstonerplots"
when (dialectName) {
"postgresql" -> run {
dataSourceClassName = "org.postgresql.ds.PGSimpleDataSource"
dataSourceProperties["serverName"] = address
dataSourceProperties["portNumber"] = port.toString()
dataSourceProperties["databaseName"] = dco.database
}
else -> throw IllegalArgumentException("Unsupported dialect: $dialectName")
}
poolName = "parcels"
maximumPoolSize = dco.poolSize
dataSourceClassName = driver
username = dco.username
password = dco.password
connectionTimeout = 15000
leakDetectionThreshold = 10000
connectionTestQuery = "SELECT 1"
/*
addDataSourceProperty("serverName", address)
addDataSourceProperty("port", port.toString())
addDataSourceProperty("databaseName", dco.database)
@@ -32,7 +41,7 @@ fun getHikariDataSource(dialectName: String,
dataSourceProperties.remove("port")
dataSourceProperties.remove("databaseName")
addDataSourceProperty("url", "jdbc:h2:${if (address.isBlank()) "" else "tcp://$address/"}~/${dco.database}")
} else {
} else if (dialectName.toLowerCase() == "mysql") {
// doesn't exist on the MariaDB driver
addDataSourceProperty("cachePrepStmts", "true")
addDataSourceProperty("alwaysSendSetIsolation", "false")
@@ -49,8 +58,7 @@ fun getHikariDataSource(dialectName: String,
// make sure unicode characters can be used.
addDataSourceProperty("characterEncoding", "utf8")
addDataSourceProperty("useUnicode", "true")
}
HikariDataSource(this)
} else {
}*/
}

View File

@@ -1,5 +1,6 @@
package io.dico.parcels2.storage
import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.DataConnectionOptions
import kotlin.reflect.KClass
@@ -26,21 +27,16 @@ interface StorageFactory {
class ConnectionStorageFactory : StorageFactory {
override val optionsClass = DataConnectionOptions::class
private val types: Map<String, String> = mutableMapOf(
"mysql" to "com.mysql.jdbc.jdbc2.optional.MysqlDataSource",
"h2" to "org.h2.jdbcx.JdbcDataSource"
)
private val types: List<String> = listOf("postgresql")
fun register(companion: StorageFactory.StorageFactories) {
types.keys.forEach {
companion.registerFactory(it, this)
}
types.forEach { companion.registerFactory(it, this) }
}
override fun newStorageInstance(dialect: String, options: Any): Storage {
val driverClass = types[dialect.toLowerCase()] ?: throw IllegalArgumentException("Storage dialect $dialect is not supported")
return StorageWithCoroutineBacking(ExposedBacking(getHikariDataSource(dialect, driverClass, options as DataConnectionOptions)))
val hikariConfig = getHikariConfig(dialect, options as DataConnectionOptions)
val dataSourceFactory = { HikariDataSource(hikariConfig) }
return StorageWithCoroutineBacking(ExposedBacking(dataSourceFactory))
}
}