Things be working

This commit is contained in:
Dico
2018-08-05 06:46:11 +01:00
parent 3573f9ade6
commit ba347a8053
26 changed files with 711 additions and 539 deletions
+38 -29
View File
@@ -1,41 +1,50 @@
package io.dico.parcels2 package io.dico.parcels2
import io.dico.parcels2.util.uuid
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
import java.util.UUID
typealias MutableAddedDataMap = MutableMap<UUID, AddedStatus> typealias StatusKey = PlayerProfile.Real
typealias AddedDataMap = Map<UUID, AddedStatus> typealias MutableAddedDataMap = MutableMap<StatusKey, AddedStatus>
typealias AddedDataMap = Map<StatusKey, AddedStatus>
@Suppress("FunctionName")
fun MutableAddedDataMap(): MutableAddedDataMap = hashMapOf()
interface AddedData { interface AddedData {
val addedMap: AddedDataMap val addedMap: AddedDataMap
var addedStatusOfStar: AddedStatus
fun getAddedStatus(uuid: UUID): AddedStatus fun getAddedStatus(key: StatusKey): AddedStatus
fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean fun setAddedStatus(key: StatusKey, status: AddedStatus): Boolean
fun compareAndSetAddedStatus(uuid: UUID, expect: AddedStatus, status: AddedStatus): Boolean = fun compareAndSetAddedStatus(key: StatusKey, expect: AddedStatus, status: AddedStatus): Boolean =
(getAddedStatus(uuid) == expect).also { if (it) setAddedStatus(uuid, status) } (getAddedStatus(key) == expect).also { if (it) setAddedStatus(key, status) }
fun isAllowed(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.ALLOWED fun isAllowed(key: StatusKey) = getAddedStatus(key) == AddedStatus.ALLOWED
fun allow(uuid: UUID) = setAddedStatus(uuid, AddedStatus.ALLOWED) fun allow(key: StatusKey) = setAddedStatus(key, AddedStatus.ALLOWED)
fun disallow(uuid: UUID) = compareAndSetAddedStatus(uuid, AddedStatus.ALLOWED, AddedStatus.DEFAULT) fun disallow(key: StatusKey) = compareAndSetAddedStatus(key, AddedStatus.ALLOWED, AddedStatus.DEFAULT)
fun isBanned(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.BANNED fun isBanned(key: StatusKey) = getAddedStatus(key) == AddedStatus.BANNED
fun ban(uuid: UUID) = setAddedStatus(uuid, AddedStatus.BANNED) fun ban(key: StatusKey) = setAddedStatus(key, AddedStatus.BANNED)
fun unban(uuid: UUID) = compareAndSetAddedStatus(uuid, AddedStatus.BANNED, AddedStatus.DEFAULT) fun unban(key: StatusKey) = compareAndSetAddedStatus(key, AddedStatus.BANNED, AddedStatus.DEFAULT)
fun isAllowed(player: OfflinePlayer) = isAllowed(player.uuid) fun isAllowed(player: OfflinePlayer) = isAllowed(player.statusKey)
fun allow(player: OfflinePlayer) = allow(player.uuid) fun allow(player: OfflinePlayer) = allow(player.statusKey)
fun disallow(player: OfflinePlayer) = disallow(player.uuid) fun disallow(player: OfflinePlayer) = disallow(player.statusKey)
fun isBanned(player: OfflinePlayer) = isBanned(player.uuid) fun isBanned(player: OfflinePlayer) = isBanned(player.statusKey)
fun ban(player: OfflinePlayer) = ban(player.uuid) fun ban(player: OfflinePlayer) = ban(player.statusKey)
fun unban(player: OfflinePlayer) = unban(player.uuid) fun unban(player: OfflinePlayer) = unban(player.statusKey)
} }
inline val OfflinePlayer.statusKey get() = PlayerProfile.nameless(this)
open class AddedDataHolder(override var addedMap: MutableAddedDataMap = mutableMapOf()) : AddedData { open class AddedDataHolder(override var addedMap: MutableAddedDataMap = mutableMapOf()) : AddedData {
override fun getAddedStatus(uuid: UUID): AddedStatus = addedMap.getOrDefault(uuid, AddedStatus.DEFAULT) override var addedStatusOfStar: AddedStatus = AddedStatus.DEFAULT
override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean = status.takeIf { it != AddedStatus.DEFAULT }
?.let { addedMap.put(uuid, it) != it } override fun getAddedStatus(key: StatusKey): AddedStatus = addedMap.getOrDefault(key, addedStatusOfStar)
?: addedMap.remove(uuid) != null
override fun setAddedStatus(key: StatusKey, status: AddedStatus): Boolean {
return if (status.isDefault) addedMap.remove(key) != null
else addedMap.put(key, status) != status
}
} }
enum class AddedStatus { enum class AddedStatus {
@@ -43,15 +52,15 @@ enum class AddedStatus {
ALLOWED, ALLOWED,
BANNED; BANNED;
val isDefault get() = this == DEFAULT inline val isDefault get() = this == DEFAULT
val isAllowed get() = this == ALLOWED inline val isAllowed get() = this == ALLOWED
val isBanned get() = this == BANNED inline val isBanned get() = this == BANNED
} }
interface GlobalAddedData : AddedData { interface GlobalAddedData : AddedData {
val owner: ParcelOwner val owner: PlayerProfile
} }
interface GlobalAddedDataManager { interface GlobalAddedDataManager {
operator fun get(owner: ParcelOwner): GlobalAddedData operator fun get(owner: PlayerProfile): GlobalAddedData
} }
+3 -3
View File
@@ -33,7 +33,7 @@ interface Parcel : ParcelData {
} }
interface ParcelData : AddedData { interface ParcelData : AddedData {
var owner: ParcelOwner? var owner: PlayerProfile?
val since: DateTime? val since: DateTime?
fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean
@@ -48,9 +48,9 @@ interface ParcelData : AddedData {
class ParcelDataHolder(addedMap: MutableAddedDataMap = mutableMapOf()) : AddedDataHolder(addedMap), ParcelData { class ParcelDataHolder(addedMap: MutableAddedDataMap = mutableMapOf()) : AddedDataHolder(addedMap), ParcelData {
override var owner: ParcelOwner? = null override var owner: PlayerProfile? = null
override var since: DateTime? = null override var since: DateTime? = null
override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = isAllowed(player.uniqueId) override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = isAllowed(player.statusKey)
|| owner.let { it != null && it.matches(player, allowNameMatch = false) } || owner.let { it != null && it.matches(player, allowNameMatch = false) }
|| (checkAdmin && player is Player && player.hasBuildAnywhere) || (checkAdmin && player is Player && player.hasBuildAnywhere)
@@ -3,8 +3,6 @@ package io.dico.parcels2
import io.dico.parcels2.blockvisitor.RegionTraversal import io.dico.parcels2.blockvisitor.RegionTraversal
import io.dico.parcels2.blockvisitor.Worker import io.dico.parcels2.blockvisitor.Worker
import io.dico.parcels2.blockvisitor.WorktimeLimiter import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.defaultimpl.DefaultParcelGenerator
import io.dico.parcels2.options.GeneratorOptions
import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.Vec2i
import org.bukkit.Chunk import org.bukkit.Chunk
import org.bukkit.Location import org.bukkit.Location
@@ -14,9 +12,7 @@ import org.bukkit.block.Block
import org.bukkit.entity.Entity import org.bukkit.entity.Entity
import org.bukkit.generator.BlockPopulator import org.bukkit.generator.BlockPopulator
import org.bukkit.generator.ChunkGenerator import org.bukkit.generator.ChunkGenerator
import java.util.HashMap
import java.util.Random import java.util.Random
import kotlin.reflect.KClass
abstract class ParcelGenerator : ChunkGenerator() { abstract class ParcelGenerator : ChunkGenerator() {
abstract val world: World abstract val world: World
@@ -40,6 +36,7 @@ abstract class ParcelGenerator : ChunkGenerator() {
abstract fun makeParcelLocator(container: ParcelContainer): ParcelLocator abstract fun makeParcelLocator(container: ParcelContainer): ParcelLocator
} }
@Suppress("DeprecatedCallableAddReplaceWith")
interface ParcelBlockManager { interface ParcelBlockManager {
val world: World val world: World
val worktimeLimiter: WorktimeLimiter val worktimeLimiter: WorktimeLimiter
@@ -48,7 +45,7 @@ interface ParcelBlockManager {
fun getHomeLocation(parcel: ParcelId): Location fun getHomeLocation(parcel: ParcelId): Location
fun setOwnerBlock(parcel: ParcelId, owner: ParcelOwner?) fun setOwnerBlock(parcel: ParcelId, owner: PlayerProfile?)
@Deprecated("") @Deprecated("")
fun getEntities(parcel: ParcelId): Collection<Entity> = TODO() fun getEntities(parcel: ParcelId): Collection<Entity> = TODO()
@@ -1,52 +0,0 @@
@file:Suppress("unused", "UsePropertyAccessSyntax", "DEPRECATION")
package io.dico.parcels2
import io.dico.parcels2.util.getPlayerNameOrDefault
import io.dico.parcels2.util.isValid
import io.dico.parcels2.util.uuid
import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player
import java.util.UUID
class ParcelOwner(val uuid: UUID?,
val name: String?) {
val notNullName: String by lazy { name ?: getPlayerNameOrDefault(uuid!!) }
constructor(name: String) : this(null, name)
constructor(uuid: UUID) : this(uuid, null)
constructor(player: OfflinePlayer) : this(player.uuid, player.name)
companion object {
fun nameless(player: OfflinePlayer) = ParcelOwner(player.uuid, null)
}
inline val hasUUID: Boolean get() = uuid != null
val onlinePlayer: Player? get() = uuid?.let { Bukkit.getPlayer(uuid) }
val onlinePlayerAllowingNameMatch: Player? get() = onlinePlayer ?: name?.let { Bukkit.getPlayerExact(name) }
val offlinePlayer: OfflinePlayer? get() = uuid?.let { Bukkit.getOfflinePlayer(it).takeIf { it.isValid } }
val offlinePlayerAllowingNameMatch: OfflinePlayer?
get() = offlinePlayer ?: Bukkit.getOfflinePlayer(name).takeIf { it.isValid }
fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean {
return uuid?.let { it == player.uniqueId } ?: false
|| (allowNameMatch && name?.let { it == player.name } ?: false)
}
fun equals(other: ParcelOwner): Boolean {
return if (hasUUID) other.hasUUID && uuid == other.uuid
else !other.hasUUID && name == other.name
}
override fun equals(other: Any?): Boolean {
return other is ParcelOwner && equals(other)
}
override fun hashCode(): Int {
return if (hasUUID) uuid!!.hashCode() else name!!.hashCode()
}
}
@@ -42,6 +42,7 @@ class ParcelsPlugin : JavaPlugin() {
override fun onEnable() { override fun onEnable() {
plogger.info("Debug enabled: ${plogger.isDebugEnabled}") plogger.info("Debug enabled: ${plogger.isDebugEnabled}")
plogger.debug(System.getProperty("user.dir"))
if (!init()) { if (!init()) {
Bukkit.getPluginManager().disablePlugin(this) Bukkit.getPluginManager().disablePlugin(this)
} }
@@ -0,0 +1,287 @@
@file:Suppress("unused", "UsePropertyAccessSyntax", "DEPRECATION")
package io.dico.parcels2
import io.dico.parcels2.storage.Storage
import io.dico.parcels2.util.getPlayerNameOrDefault
import io.dico.parcels2.util.isValid
import io.dico.parcels2.util.uuid
import kotlinx.coroutines.experimental.Deferred
import kotlinx.coroutines.experimental.Unconfined
import kotlinx.coroutines.experimental.async
import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer
import java.util.UUID
interface PlayerProfile {
val uuid: UUID? get() = null
val name: String?
val notNullName: String
val isStar: Boolean get() = false
fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean
fun equals(other: PlayerProfile): Boolean
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
val isFake: Boolean get() = this is Fake
val isReal: Boolean get() = this is Real
companion object {
fun safe(uuid: UUID?, name: String?): PlayerProfile? {
if (uuid != null) return Real(uuid, name)
if (name != null) return invoke(name)
return null
}
operator fun invoke(uuid: UUID?, name: String?): PlayerProfile {
return safe(uuid, name) ?: throw IllegalArgumentException("One of uuid and name must not be null")
}
operator fun invoke(uuid: UUID): Real {
if (uuid == Star.uuid) return Star
return RealImpl(uuid, null)
}
operator fun invoke(name: String): PlayerProfile {
if (name == Star.name) return Star
return Fake(name)
}
operator fun invoke(player: OfflinePlayer): PlayerProfile {
return if (player.isValid) Real(player.uuid, player.name) else Fake(player.name)
}
fun nameless(player: OfflinePlayer): Real {
if (!player.isValid) throw IllegalArgumentException("The given OfflinePlayer is not valid")
return RealImpl(player.uuid, null)
}
fun byName(input: String, allowReal: Boolean = true, allowFake: Boolean = false): PlayerProfile {
if (!allowReal) {
if (!allowFake) throw IllegalArgumentException("at least one of allowReal and allowFake must be true")
return Fake(input)
}
if (input == Star.name) return Star
return Bukkit.getOfflinePlayer(input).takeIf { it.isValid }?.let { PlayerProfile(it) }
?: Unresolved(input)
}
}
interface Real : PlayerProfile {
override val uuid: UUID
override val notNullName: String
get() = name ?: getPlayerNameOrDefault(uuid)
val player: OfflinePlayer? get() = Bukkit.getOfflinePlayer(uuid).takeIf { it.isValid }
val playerUnchecked: OfflinePlayer get() = Bukkit.getOfflinePlayer(uuid)
override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
return uuid == player.uuid || (allowNameMatch && name?.let { it == player.name } == true)
}
override fun equals(other: PlayerProfile): Boolean {
return other is Real && uuid == other.uuid
}
companion object {
fun byName(name: String): PlayerProfile {
if (name == Star.name) return Star
return Unresolved(name)
}
operator fun invoke(uuid: UUID, name: String?): Real {
if (name == Star.name || uuid == Star.uuid) return Star
return RealImpl(uuid, name)
}
fun safe(uuid: UUID?, name: String?): Real? {
if (name == Star.name || uuid == Star.uuid) return Star
if (uuid == null) return null
return RealImpl(uuid, name)
}
}
}
object Star : BaseImpl(), Real {
override val name: String = "*"
override val uuid: UUID = UUID.fromString("7d09c4c6-117d-4f36-9778-c4d24618cee1")
override val isStar: Boolean get() = true
override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
return true
}
}
abstract class NameOnly(override val name: String) : BaseImpl() {
override val notNullName get() = name
override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
return allowNameMatch && player.name == name
}
}
class Fake(name: String) : NameOnly(name) {
override fun equals(other: PlayerProfile): Boolean {
return other is Fake && other.name == name
}
}
class Unresolved(name: String) : NameOnly(name) {
override fun equals(other: PlayerProfile): Boolean {
return other is Unresolved && name == other.name
}
fun tryResolve(storage: Storage): Deferred<Real?> {
return async(Unconfined) { tryResolveSuspendedly(storage) }
}
suspend fun tryResolveSuspendedly(storage: Storage): Real? {
return storage.getPlayerUuidForName(name).await()?.let { RealImpl(it, name) }
}
fun resolve(uuid: UUID): Real {
return RealImpl(uuid, name)
}
fun throwException(): Nothing {
throw IllegalArgumentException("A UUID for the player $name can not be found")
}
}
abstract class BaseImpl : PlayerProfile {
override fun equals(other: Any?): Boolean {
return this === other || (other is PlayerProfile && equals(other))
}
override fun hashCode(): Int {
return uuid?.hashCode() ?: name!!.hashCode()
}
}
private class RealImpl(override val uuid: UUID, override val name: String?) : BaseImpl(), Real
}
/*
/**
* This class can represent:
*
* An existing player
* A fake player (with only a name)
* An existing player who must have its uuid resolved from the database (after checking against Bukkit OfflinePlayer)
* STAR profile, which matches everyone. This profile is considered a REAL player, because it can have an added status.
*/
class PlayerProfile2 private constructor(uuid: UUID?,
val name: String?,
val isReal: Boolean = uuid != null) {
private var _uuid: UUID? = uuid
val notNullName: String get() = name ?: getPlayerNameOrDefault(uuid!!)
val uuid: UUID? get() = _uuid ?: if (isReal) throw IllegalStateException("This PlayerProfile must be resolved first") else null
companion object {
// below uuid is just a randomly generated one (version 4). Hopefully no minecraft player will ever have it :)
val star = PlayerProfile(UUID.fromString("7d09c4c6-117d-4f36-9778-c4d24618cee1"), "*", true)
fun nameless(player: OfflinePlayer): PlayerProfile {
if (!player.isValid) throw IllegalArgumentException("The given OfflinePlayer is not valid")
return PlayerProfile(player.uuid)
}
fun fromNameAndUuid(name: String?, uuid: UUID?): PlayerProfile? {
if (name == null && uuid == null) return null
if (star.name == name && star._uuid == uuid) return star
return PlayerProfile(uuid, name)
}
fun realPlayerByName(name: String): PlayerProfile {
return fromString(name, allowReal = true, allowFake = false)
}
fun fromString(input: String, allowReal: Boolean = true, allowFake: Boolean = false): PlayerProfile {
if (!allowReal) {
if (!allowFake) throw IllegalArgumentException("at least one of allowReal and allowFake must be true")
return PlayerProfile(input)
}
if (input == star.name) return star
return Bukkit.getOfflinePlayer(input).takeIf { it.isValid }?.let { PlayerProfile(it) }
?: PlayerProfile(null, input, !allowFake)
}
operator fun invoke(name: String): PlayerProfile {
if (name == star.name) return star
return PlayerProfile(null, name)
}
operator fun invoke(uuid: UUID): PlayerProfile {
if (uuid == star.uuid) return star
return PlayerProfile(uuid, null)
}
operator fun invoke(player: OfflinePlayer): PlayerProfile {
// avoid UUID comparison against STAR
return if (player.isValid) PlayerProfile(player.uuid, player.name) else invoke(player.name)
}
}
val isStar: Boolean get() = this === star || (name == star.name && _uuid == star._uuid)
val hasUUID: Boolean get() = _uuid != null
val mustBeResolved: Boolean get() = isReal && _uuid == null
val onlinePlayer: Player? get() = uuid?.let { Bukkit.getPlayer(uuid) }
val onlinePlayerAllowingNameMatch: Player? get() = onlinePlayer ?: name?.let { Bukkit.getPlayerExact(name) }
val offlinePlayer: OfflinePlayer? get() = uuid?.let { Bukkit.getOfflinePlayer(it).takeIf { it.isValid } }
val offlinePlayerAllowingNameMatch: OfflinePlayer?
get() = offlinePlayer ?: Bukkit.getOfflinePlayer(name).takeIf { it.isValid }
fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean {
if (isStar) return true
return uuid?.let { it == player.uniqueId } ?: false
|| (allowNameMatch && name?.let { it == player.name } ?: false)
}
fun equals(other: PlayerProfile): Boolean {
return if (_uuid != null) _uuid == other._uuid
else other._uuid == null && isReal == other.isReal && name == other.name
}
override fun equals(other: Any?): Boolean {
return other is PlayerProfile && equals(other)
}
override fun hashCode(): Int {
return _uuid?.hashCode() ?: name!!.hashCode()
}
/**
* resolve the uuid of this player profile if [mustBeResolved], using specified [storage].
* returns true if the PlayerProfile has a uuid after this call.
*/
suspend fun resolve(storage: Storage): Boolean {
if (mustBeResolved) {
val uuid = storage.getPlayerUuidForName(name!!).await()
_uuid = uuid
return uuid != null
}
return _uuid != null
}
fun resolve(uuid: UUID) {
if (isReal && _uuid == null) {
_uuid = uuid
}
}
}
*/
@@ -3,7 +3,7 @@ package io.dico.parcels2.command
import io.dico.dicore.command.CommandException import io.dico.dicore.command.CommandException
import io.dico.dicore.command.ExecutionContext import io.dico.dicore.command.ExecutionContext
import io.dico.dicore.command.ICommandReceiver import io.dico.dicore.command.ICommandReceiver
import io.dico.parcels2.ParcelOwner import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.ParcelWorld import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.util.hasAdminManage import io.dico.parcels2.util.hasAdminManage
@@ -31,7 +31,7 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei
protected suspend fun checkParcelLimit(player: Player, world: ParcelWorld) { protected suspend fun checkParcelLimit(player: Player, world: ParcelWorld) {
if (player.hasAdminManage) return if (player.hasAdminManage) return
val numOwnedParcels = plugin.storage.getOwnedParcels(ParcelOwner(player)).await() val numOwnedParcels = plugin.storage.getOwnedParcels(PlayerProfile(player)).await()
.filter { it.worldId.equals(world.id) }.size .filter { it.worldId.equals(world.id) }.size
val limit = player.parcelLimit val limit = player.parcelLimit
@@ -5,7 +5,7 @@ import io.dico.dicore.command.annotation.Cmd
import io.dico.dicore.command.annotation.Desc import io.dico.dicore.command.annotation.Desc
import io.dico.parcels2.GlobalAddedData import io.dico.parcels2.GlobalAddedData
import io.dico.parcels2.GlobalAddedDataManager import io.dico.parcels2.GlobalAddedDataManager
import io.dico.parcels2.ParcelOwner import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player import org.bukkit.entity.Player
@@ -13,7 +13,7 @@ import org.bukkit.entity.Player
class CommandsAddedStatusGlobal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { class CommandsAddedStatusGlobal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
private inline val data get() = plugin.globalAddedData private inline val data get() = plugin.globalAddedData
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
private inline operator fun GlobalAddedDataManager.get(player: OfflinePlayer): GlobalAddedData = data[ParcelOwner(player)] private inline operator fun GlobalAddedDataManager.get(player: OfflinePlayer): GlobalAddedData = data[PlayerProfile(player)]
@Cmd("allow", aliases = ["add", "permit"]) @Cmd("allow", aliases = ["add", "permit"])
@Desc("Globally allows a player to build on all", @Desc("Globally allows a player to build on all",
@@ -6,7 +6,7 @@ import io.dico.dicore.command.annotation.Cmd
import io.dico.dicore.command.annotation.Desc import io.dico.dicore.command.annotation.Desc
import io.dico.dicore.command.annotation.Flag import io.dico.dicore.command.annotation.Flag
import io.dico.dicore.command.annotation.RequireParameters import io.dico.dicore.command.annotation.RequireParameters
import io.dico.parcels2.ParcelOwner import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.util.hasAdminManage import io.dico.parcels2.util.hasAdminManage
import io.dico.parcels2.util.hasParcelHomeOthers import io.dico.parcels2.util.hasParcelHomeOthers
@@ -25,7 +25,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
val parcel = world.nextEmptyParcel() val parcel = world.nextEmptyParcel()
?: error("This world is full, please ask an admin to upsize it") ?: error("This world is full, please ask an admin to upsize it")
parcel.owner = ParcelOwner(uuid = player.uuid) parcel.owner = PlayerProfile(uuid = player.uuid)
player.teleport(parcel.world.getHomeLocation(parcel.id)) player.teleport(parcel.world.getHomeLocation(parcel.id))
return "Enjoy your new parcel!" return "Enjoy your new parcel!"
} }
@@ -72,7 +72,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
} }
checkParcelLimit(player, world) checkParcelLimit(player, world)
parcel.owner = ParcelOwner(player) parcel.owner = PlayerProfile(player)
return "Enjoy your new parcel!" return "Enjoy your new parcel!"
} }
@@ -7,14 +7,14 @@ import io.dico.dicore.command.parameter.type.ParameterType
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.floor import io.dico.parcels2.util.floor
import io.dico.parcels2.util.isValid
import kotlinx.coroutines.experimental.Deferred import kotlinx.coroutines.experimental.Deferred
import org.bukkit.Bukkit
import org.bukkit.command.CommandSender import org.bukkit.command.CommandSender
import org.bukkit.entity.Player import org.bukkit.entity.Player
sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) { sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
abstract suspend fun ParcelsPlugin.getParcelSuspend(): Parcel? abstract suspend fun ParcelsPlugin.getParcelSuspend(): Parcel?
fun ParcelsPlugin.getParcelDeferred(): Deferred<Parcel?> = functionHelper.deferUndispatchedOnMainThread { getParcelSuspend() } fun ParcelsPlugin.getParcelDeferred(): Deferred<Parcel?> = functionHelper.deferUndispatchedOnMainThread { getParcelSuspend() }
class ByID(world: ParcelWorld, val id: Vec2i?, isDefault: Boolean) : ParcelTarget(world, isDefault) { class ByID(world: ParcelWorld, val id: Vec2i?, isDefault: Boolean) : ParcelTarget(world, isDefault) {
@@ -23,16 +23,35 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
val isPath: Boolean get() = id == null val isPath: Boolean get() = id == null
} }
class ByOwner(world: ParcelWorld, val owner: ParcelOwner, val index: Int, isDefault: Boolean) : ParcelTarget(world, isDefault) { class ByOwner(world: ParcelWorld,
owner: PlayerProfile,
val index: Int,
isDefault: Boolean,
val onResolveFailure: (() -> Unit)? = null) : ParcelTarget(world, isDefault) {
init { init {
if (index < 0) throw IllegalArgumentException("Invalid parcel home index: $index") if (index < 0) throw IllegalArgumentException("Invalid parcel home index: $index")
} }
var owner = owner; private set
override suspend fun ParcelsPlugin.getParcelSuspend(): Parcel? { override suspend fun ParcelsPlugin.getParcelSuspend(): Parcel? {
onResolveFailure?.let { onFail ->
val owner = owner
if (owner is PlayerProfile.Unresolved) {
val new = owner.tryResolveSuspendedly(storage)
if (new == null) {
onFail()
return@let
}
this@ByOwner.owner = new
}
}
val ownedParcelsSerialized = storage.getOwnedParcels(owner).await() val ownedParcelsSerialized = storage.getOwnedParcels(owner).await()
val ownedParcels = ownedParcelsSerialized val ownedParcels = ownedParcelsSerialized
.map { parcelProvider.getParcelById(it) } .map { parcelProvider.getParcelById(it) }
.filter { it != null && world == it.world && owner == it.owner } .filter { it != null && world == it.world && owner == it.owner }
return ownedParcels.getOrNull(index) return ownedParcels.getOrNull(index)
} }
} }
@@ -80,8 +99,8 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
} }
if (kind and OWNER == 0) invalidInput(parameter, "You must specify a parcel by ID, that is, the x and z component separated by a comma") if (kind and OWNER == 0) invalidInput(parameter, "You must specify a parcel by ID, that is, the x and z component separated by a comma")
val (owner, index) = getHomeIndex(parameter, sender, input) val (owner, index) = getHomeIndex(parameter, kind, sender, input)
return ByOwner(world, owner, index, false) return ByOwner(world, owner, index, false, onResolveFailure = { invalidInput(parameter, "The player $input does not exist") })
} }
private fun getId(parameter: Parameter<*, *>, input: String): Vec2i { private fun getId(parameter: Parameter<*, *>, input: String): Vec2i {
@@ -94,7 +113,7 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
return Vec2i(x, z) return Vec2i(x, z)
} }
private fun getHomeIndex(parameter: Parameter<*, Int>, sender: CommandSender, input: String): Pair<ParcelOwner, Int> { private fun getHomeIndex(parameter: Parameter<*, *>, kind: Int, sender: CommandSender, input: String): Pair<PlayerProfile, Int> {
val splitIdx = input.indexOf(':') val splitIdx = input.indexOf(':')
val ownerString: String val ownerString: String
val indexString: String val indexString: String
@@ -109,9 +128,9 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
} }
val owner = if (ownerString.isEmpty()) val owner = if (ownerString.isEmpty())
ParcelOwner(requirePlayer(sender, parameter, "the player")) PlayerProfile(requirePlayer(sender, parameter, "the player"))
else else
inputAsOwner(parameter, ownerString) PlayerProfile.byName(ownerString, allowReal = kind and OWNER_REAL != 0, allowFake = kind and OWNER_FAKE != 0)
val index = if (indexString.isEmpty()) 0 else indexString.toIntOrNull() val index = if (indexString.isEmpty()) 0 else indexString.toIntOrNull()
?: invalidInput(parameter, "The home index must be an integer, $indexString is not an integer") ?: invalidInput(parameter, "The home index must be an integer, $indexString is not an integer")
@@ -124,22 +143,6 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
return sender return sender
} }
@Suppress("DEPRECATION")
private fun inputAsOwner(parameter: Parameter<*, Int>, input: String): ParcelOwner {
val kind = parameter.paramInfo ?: DEFAULT_KIND
if (kind and OWNER_REAL == 0) {
return ParcelOwner(input)
}
val player = Bukkit.getOfflinePlayer(input).takeIf { it.isValid }
if (player == null) {
if (kind and OWNER_FAKE == 0) invalidInput(parameter, "The player $input does not exist")
return ParcelOwner(input)
}
return ParcelOwner(player)
}
override fun getDefaultValue(parameter: Parameter<ParcelTarget, Int>, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget? { override fun getDefaultValue(parameter: Parameter<ParcelTarget, Int>, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget? {
val kind = parameter.paramInfo ?: DEFAULT_KIND val kind = parameter.paramInfo ?: DEFAULT_KIND
val useLocation = when { val useLocation = when {
@@ -156,7 +159,8 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
return ByID(world, id, true) return ByID(world, id, true)
} }
return ByOwner(world, ParcelOwner(player), 0, true) return ByOwner(world, PlayerProfile(player), 0, true)
} }
} }
} }
@@ -119,7 +119,7 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp
val modX = absX umod sectionSize val modX = absX umod sectionSize
val modZ = absZ umod sectionSize val modZ = absZ umod sectionSize
if (modX in 0 until parcelSize && modZ in 0 until parcelSize) { if (modX in 0 until parcelSize && modZ in 0 until parcelSize) {
return mapper((absX - modX) / sectionSize, (absZ - modZ) / sectionSize) return mapper((absX - modX) / sectionSize + 1, (absZ - modZ) / sectionSize + 1)
} }
return null return null
} }
@@ -140,16 +140,18 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp
override val world: World = this@DefaultParcelGenerator.world override val world: World = this@DefaultParcelGenerator.world
override fun getBottomBlock(parcel: ParcelId): Vec2i = Vec2i( override fun getBottomBlock(parcel: ParcelId): Vec2i = Vec2i(
sectionSize * parcel.pos.x + pathOffset + o.offsetX, sectionSize * (parcel.x - 1) + pathOffset + o.offsetX,
sectionSize * parcel.pos.z + pathOffset + o.offsetZ sectionSize * (parcel.z - 1) + pathOffset + o.offsetZ
) )
override fun getHomeLocation(parcel: ParcelId): Location { override fun getHomeLocation(parcel: ParcelId): Location {
val bottom = getBottomBlock(parcel) val bottom = getBottomBlock(parcel)
return Location(world, bottom.x.toDouble() + 0.5, o.floorHeight + 1.0, bottom.z + 0.5 + (o.parcelSize - 1) / 2.0, -90F, 0F) val x = bottom.x + (o.parcelSize - 1) / 2.0
val z = bottom.z - 2
return Location(world, x + 0.5, o.floorHeight + 1.0, z + 0.5, 0F, 0F)
} }
override fun setOwnerBlock(parcel: ParcelId, owner: ParcelOwner?) { override fun setOwnerBlock(parcel: ParcelId, owner: PlayerProfile?) {
val b = getBottomBlock(parcel) val b = getBottomBlock(parcel)
val wallBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 1) val wallBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 1)
@@ -178,8 +180,8 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp
skullBlock.type = Material.PLAYER_HEAD skullBlock.type = Material.PLAYER_HEAD
val skull = skullBlock.state as Skull val skull = skullBlock.state as Skull
if (owner.uuid != null) { if (owner is PlayerProfile.Real) {
skull.owningPlayer = owner.offlinePlayer skull.owningPlayer = owner.playerUnchecked
} else { } else {
skull.owner = owner.name skull.owner = owner.name
} }
@@ -3,33 +3,32 @@
package io.dico.parcels2.defaultimpl package io.dico.parcels2.defaultimpl
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.util.alsoIfTrue
import java.util.Collections import java.util.Collections
import java.util.UUID
class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataManager { class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataManager {
private val map = mutableMapOf<ParcelOwner, GlobalAddedData>() private val map = mutableMapOf<PlayerProfile, GlobalAddedData>()
override fun get(owner: ParcelOwner): GlobalAddedData { override fun get(owner: PlayerProfile): GlobalAddedData {
return map[owner] ?: GlobalAddedDataImpl(owner).also { map[owner] = it } return map[owner] ?: GlobalAddedDataImpl(owner).also { map[owner] = it }
} }
private inner class GlobalAddedDataImpl(override val owner: ParcelOwner, private inner class GlobalAddedDataImpl(override val owner: PlayerProfile,
data: MutableAddedDataMap = emptyData) data: MutableAddedDataMap = emptyData)
: AddedDataHolder(data), GlobalAddedData { : AddedDataHolder(data), GlobalAddedData {
private inline var data get() = addedMap; set(value) = run { addedMap = value } private inline var data get() = addedMap; set(value) = run { addedMap = value }
private inline val isEmpty get() = data === emptyData private inline val isEmpty get() = data === emptyData
override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean { override fun setAddedStatus(key: StatusKey, status: AddedStatus): Boolean {
if (isEmpty) { if (isEmpty) {
if (status == AddedStatus.DEFAULT) return false if (status == AddedStatus.DEFAULT) return false
data = mutableMapOf() data = mutableMapOf()
} }
return super.setAddedStatus(uuid, status).also { return super.setAddedStatus(key, status).alsoIfTrue {
if (it) plugin.storage.setGlobalAddedStatus(owner, uuid, status) plugin.storage.setGlobalAddedStatus(owner, key, status)
} }
} }
} }
private companion object { private companion object {
@@ -3,6 +3,7 @@ package io.dico.parcels2.defaultimpl
import io.dico.dicore.Formatting import io.dico.dicore.Formatting
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.alsoIfTrue
import io.dico.parcels2.util.getPlayerName import io.dico.parcels2.util.getPlayerName
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
import org.joda.time.DateTime import org.joda.time.DateTime
@@ -33,20 +34,24 @@ class ParcelImpl(override val world: ParcelWorld,
world.storage.setParcelData(this, null) world.storage.setParcelData(this, null)
} }
override val addedMap: Map<UUID, AddedStatus> get() = data.addedMap override val addedMap: AddedDataMap get() = data.addedMap
override fun getAddedStatus(uuid: UUID) = data.getAddedStatus(uuid) override fun getAddedStatus(key: StatusKey) = data.getAddedStatus(key)
override fun isBanned(uuid: UUID) = data.isBanned(uuid) override fun isBanned(key: StatusKey) = data.isBanned(key)
override fun isAllowed(uuid: UUID) = data.isAllowed(uuid) override fun isAllowed(key: StatusKey) = data.isAllowed(key)
override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean): Boolean { override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean): Boolean {
return (data.canBuild(player, checkAdmin, false)) return (data.canBuild(player, checkAdmin, false))
|| checkGlobal && world.globalAddedData[owner ?: return false].isAllowed(player) || checkGlobal && world.globalAddedData[owner ?: return false].isAllowed(player)
} }
val globalAddedMap: Map<UUID, AddedStatus>? get() = owner?.let { world.globalAddedData[it].addedMap } override var addedStatusOfStar: AddedStatus
get() = data.addedStatusOfStar
set(value) = run { setAddedStatus(PlayerProfile.Star, value) }
val globalAddedMap: AddedDataMap? get() = owner?.let { world.globalAddedData[it].addedMap }
override val since: DateTime? get() = data.since override val since: DateTime? get() = data.since
override var owner: ParcelOwner? override var owner: PlayerProfile?
get() = data.owner get() = data.owner
set(value) { set(value) {
if (data.owner != value) { if (data.owner != value) {
@@ -55,9 +60,9 @@ class ParcelImpl(override val world: ParcelWorld,
} }
} }
override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean { override fun setAddedStatus(key: StatusKey, status: AddedStatus): Boolean {
return data.setAddedStatus(uuid, status).also { return data.setAddedStatus(key, status).alsoIfTrue {
if (it) world.storage.setParcelPlayerStatus(this, uuid, status) world.storage.setParcelPlayerStatus(this, key, status)
} }
} }
@@ -102,10 +107,10 @@ private object ParcelInfoStringComputer {
append(' ') append(' ')
} }
private fun StringBuilder.appendAddedList(local: Map<UUID, AddedStatus>, global: Map<UUID, AddedStatus>, status: AddedStatus, fieldName: String) { private fun StringBuilder.appendAddedList(local: AddedDataMap, global: AddedDataMap, status: AddedStatus, fieldName: String) {
val globalSet = global.filterValues { it == status }.keys val globalSet = global.filterValues { it == status }.keys
val localList = local.filterValues { it == status }.keys.filter { it !in globalSet } val localList = local.filterValues { it == status }.keys.filter { it !in globalSet }
val stringList = globalSet.map(::getPlayerName).map { "(G)$it" } + localList.map(::getPlayerName) val stringList = globalSet.map(StatusKey::notNullName).map { "(G)$it" } + localList.map(StatusKey::notNullName)
if (stringList.isEmpty()) return if (stringList.isEmpty()) return
appendField({ appendField({
@@ -75,7 +75,7 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
} }
logger.info("Loading all parcel data...") logger.info("Loading all parcel data...")
val channel = plugin.storage.readAllParcelData() val channel = plugin.storage.transmitAllParcelData()
do { do {
val pair = channel.receiveOrNull() ?: break val pair = channel.receiveOrNull() ?: break
val parcel = getParcelById(pair.first) ?: continue val parcel = getParcelById(pair.first) ?: continue
@@ -119,7 +119,7 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
} }
} }
val channel = plugin.storage.readAllParcelData() val channel = plugin.storage.transmitAllParcelData()
val job = plugin.functionHelper.launchLazilyOnMainThread { val job = plugin.functionHelper.launchLazilyOnMainThread {
do { do {
val pair = channel.receiveOrNull() ?: break val pair = channel.receiveOrNull() ?: break
@@ -6,6 +6,7 @@ import io.dico.dicore.RegistratorListener
import io.dico.parcels2.Parcel import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelProvider import io.dico.parcels2.ParcelProvider
import io.dico.parcels2.ParcelWorld import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.statusKey
import io.dico.parcels2.util.* import io.dico.parcels2.util.*
import org.bukkit.Material.* import org.bukkit.Material.*
import org.bukkit.World import org.bukkit.World
@@ -51,7 +52,7 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: Par
val user = event.player val user = event.player
if (user.hasBanBypass) return@l if (user.hasBanBypass) return@l
val parcel = parcelProvider.getParcelAt(event.to) ?: return@l val parcel = parcelProvider.getParcelAt(event.to) ?: return@l
if (parcel.isBanned(user.uuid)) { if (parcel.isBanned(user.statusKey)) {
parcelProvider.getParcelAt(event.from)?.also { parcelProvider.getParcelAt(event.from)?.also {
user.teleport(it.world.getHomeLocation(it.id)) user.teleport(it.world.getHomeLocation(it.id))
user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel") user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel")
@@ -178,7 +179,7 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: Par
val clickedBlock = event.clickedBlock val clickedBlock = event.clickedBlock
val parcel = clickedBlock?.let { world.getParcelAt(it) } val parcel = clickedBlock?.let { world.getParcelAt(it) }
if (!user.hasBuildAnywhere && parcel.isPresentAnd { isBanned(user.uuid) }) { if (!user.hasBuildAnywhere && parcel.isPresentAnd { isBanned(user.statusKey) }) {
user.sendParcelMessage(nopermit = true, message = "You cannot interact with parcels you're banned from") user.sendParcelMessage(nopermit = true, message = "You cannot interact with parcels you're banned from")
event.isCancelled = true; return@l event.isCancelled = true; return@l
} }
@@ -2,10 +2,8 @@ package io.dico.parcels2.storage
import io.dico.parcels2.* import io.dico.parcels2.*
import kotlinx.coroutines.experimental.CoroutineDispatcher import kotlinx.coroutines.experimental.CoroutineDispatcher
import kotlinx.coroutines.experimental.CoroutineScope
import kotlinx.coroutines.experimental.Deferred import kotlinx.coroutines.experimental.Deferred
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.channels.ProducerScope
import kotlinx.coroutines.experimental.channels.ReceiveChannel import kotlinx.coroutines.experimental.channels.ReceiveChannel
import kotlinx.coroutines.experimental.channels.SendChannel import kotlinx.coroutines.experimental.channels.SendChannel
import java.util.UUID import java.util.UUID
@@ -16,58 +14,49 @@ interface Backing {
val isConnected: Boolean val isConnected: Boolean
val dispatcher: CoroutineDispatcher
fun launchJob(job: Backing.() -> Unit): Job fun launchJob(job: Backing.() -> Unit): Job
fun <T> launchFuture(future: Backing.() -> T): Deferred<T> fun <T> launchFuture(future: Backing.() -> T): Deferred<T>
fun <T> openChannel(future: Backing.(SendChannel<T>) -> Unit): ReceiveChannel<T> fun <T> openChannel(future: Backing.(SendChannel<T>) -> Unit): ReceiveChannel<T>
fun <T> openChannelForWriting(future: Backing.(T) -> Unit): SendChannel<T>
fun init() fun init()
fun shutdown() fun shutdown()
/** fun getPlayerUuidForName(name: String): UUID?
* 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.
*/
fun produceParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>)
fun produceAllParcelData(channel: SendChannel<DataPair>) fun transmitParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>)
fun transmitAllParcelData(channel: SendChannel<DataPair>)
fun readParcelData(parcel: ParcelId): ParcelData? fun readParcelData(parcel: ParcelId): ParcelData?
fun getOwnedParcels(user: ParcelOwner): List<ParcelId> fun getOwnedParcels(user: PlayerProfile): List<ParcelId>
fun getNumParcels(user: ParcelOwner): Int = getOwnedParcels(user).size fun getNumParcels(user: PlayerProfile): Int = getOwnedParcels(user).size
fun setParcelData(parcel: ParcelId, data: ParcelData?) fun setParcelData(parcel: ParcelId, data: ParcelData?)
fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?) fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?)
fun setLocalPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus) fun setLocalPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus)
fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean)
fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean)
fun produceAllGlobalAddedData(channel: SendChannel<AddedDataPair<ParcelOwner>>) fun transmitAllGlobalAddedData(channel: SendChannel<AddedDataPair<PlayerProfile>>)
fun readGlobalAddedData(owner: ParcelOwner): MutableAddedDataMap fun readGlobalAddedData(owner: PlayerProfile): MutableAddedDataMap
fun setGlobalPlayerStatus(owner: ParcelOwner, player: UUID, status: AddedStatus)
}
abstract class AbstractBacking(val dispatcher: CoroutineDispatcher) {
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 setGlobalPlayerStatus(owner: PlayerProfile, player: PlayerProfile, status: AddedStatus)
} }
@@ -6,6 +6,8 @@ import io.dico.parcels2.*
import kotlinx.coroutines.experimental.Deferred import kotlinx.coroutines.experimental.Deferred
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.channels.ReceiveChannel import kotlinx.coroutines.experimental.channels.ReceiveChannel
import kotlinx.coroutines.experimental.channels.SendChannel
import kotlinx.coroutines.experimental.launch
import java.util.UUID import java.util.UUID
typealias DataPair = Pair<ParcelId, ParcelData?> typealias DataPair = Pair<ParcelId, ParcelData?>
@@ -20,68 +22,77 @@ interface Storage {
fun shutdown(): Job fun shutdown(): Job
fun getPlayerUuidForName(name: String): Deferred<UUID?>
fun readParcelData(parcel: ParcelId): Deferred<ParcelData?> fun readParcelData(parcel: ParcelId): Deferred<ParcelData?>
fun readParcelData(parcels: Sequence<ParcelId>): ReceiveChannel<DataPair> fun transmitParcelData(parcels: Sequence<ParcelId>): ReceiveChannel<DataPair>
fun readAllParcelData(): ReceiveChannel<DataPair> fun transmitAllParcelData(): ReceiveChannel<DataPair>
fun getOwnedParcels(user: ParcelOwner): Deferred<List<ParcelId>> fun getOwnedParcels(user: PlayerProfile): Deferred<List<ParcelId>>
fun getNumParcels(user: ParcelOwner): Deferred<Int> fun getNumParcels(user: PlayerProfile): Deferred<Int>
fun setParcelData(parcel: ParcelId, data: ParcelData?): Job fun setParcelData(parcel: ParcelId, data: ParcelData?): Job
fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?): Job fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?): Job
fun setParcelPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus): Job fun setParcelPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus): Job
fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean): Job fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean): Job
fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean): Job fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean): Job
fun readAllGlobalAddedData(): ReceiveChannel<AddedDataPair<ParcelOwner>> fun transmitAllGlobalAddedData(): ReceiveChannel<AddedDataPair<PlayerProfile>>
fun readGlobalAddedData(owner: ParcelOwner): Deferred<MutableAddedDataMap?> fun readGlobalAddedData(owner: PlayerProfile): Deferred<MutableAddedDataMap?>
fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus): Job fun setGlobalAddedStatus(owner: PlayerProfile, player: PlayerProfile, status: AddedStatus): Job
fun getChannelToUpdateParcelData(): SendChannel<Pair<ParcelId, ParcelData>>
} }
class BackedStorage internal constructor(val b: Backing) : Storage { class BackedStorage internal constructor(val b: Backing) : Storage {
override val name get() = b.name override val name get() = b.name
override val isConnected get() = b.isConnected override val isConnected get() = b.isConnected
override fun init() = b.launchJob { init() } override fun init() = launch(b.dispatcher) { b.init() }
override fun shutdown() = b.launchJob { shutdown() } override fun shutdown() = launch(b.dispatcher) { b.shutdown() }
override fun readParcelData(parcel: ParcelId) = b.launchFuture { readParcelData(parcel) } override fun getPlayerUuidForName(name: String): Deferred<UUID?> = b.launchFuture { b.getPlayerUuidForName(name) }
override fun readParcelData(parcels: Sequence<ParcelId>) = b.openChannel<DataPair> { produceParcelData(it, parcels) } override fun readParcelData(parcel: ParcelId) = b.launchFuture { b.readParcelData(parcel) }
override fun readAllParcelData() = b.openChannel<DataPair> { produceAllParcelData(it) } override fun transmitParcelData(parcels: Sequence<ParcelId>) = b.openChannel<DataPair> { b.transmitParcelData(it, parcels) }
override fun getOwnedParcels(user: ParcelOwner) = b.launchFuture { getOwnedParcels(user) } override fun transmitAllParcelData() = b.openChannel<DataPair> { b.transmitAllParcelData(it) }
override fun getNumParcels(user: ParcelOwner) = b.launchFuture { getNumParcels(user) } override fun getOwnedParcels(user: PlayerProfile) = b.launchFuture { b.getOwnedParcels(user) }
override fun setParcelData(parcel: ParcelId, data: ParcelData?) = b.launchJob { setParcelData(parcel, data) } override fun getNumParcels(user: PlayerProfile) = b.launchFuture { b.getNumParcels(user) }
override fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?) = b.launchJob { setParcelOwner(parcel, owner) } override fun setParcelData(parcel: ParcelId, data: ParcelData?) = b.launchJob { b.setParcelData(parcel, data) }
override fun setParcelPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus) = b.launchJob { setLocalPlayerStatus(parcel, player, status) } override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) = b.launchJob { b.setParcelOwner(parcel, owner) }
override fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) = b.launchJob { setParcelAllowsInteractInventory(parcel, value) } override fun setParcelPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus) = b.launchJob { b.setLocalPlayerStatus(parcel, player, status) }
override fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) = b.launchJob { setParcelAllowsInteractInputs(parcel, value) } override fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) = b.launchJob { b.setParcelAllowsInteractInventory(parcel, value) }
override fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) = b.launchJob { b.setParcelAllowsInteractInputs(parcel, value) }
override fun readAllGlobalAddedData(): ReceiveChannel<AddedDataPair<ParcelOwner>> = b.openChannel { produceAllGlobalAddedData(it) } override fun transmitAllGlobalAddedData(): ReceiveChannel<AddedDataPair<PlayerProfile>> = b.openChannel { b.transmitAllGlobalAddedData(it) }
override fun readGlobalAddedData(owner: ParcelOwner): Deferred<MutableAddedDataMap?> = b.launchFuture { readGlobalAddedData(owner) } override fun readGlobalAddedData(owner: PlayerProfile): Deferred<MutableAddedDataMap?> = b.launchFuture { b.readGlobalAddedData(owner) }
override fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) = b.launchJob { setGlobalPlayerStatus(owner, player, status) } override fun setGlobalAddedStatus(owner: PlayerProfile, player: PlayerProfile, status: AddedStatus) = b.launchJob { b.setGlobalPlayerStatus(owner, player, status) }
override fun getChannelToUpdateParcelData(): SendChannel<Pair<ParcelId, ParcelData>> = b.openChannelForWriting { b.setParcelData(it.first, it.second) }
} }
@@ -1,118 +0,0 @@
package io.dico.parcels2.storage.exposed
import kotlinx.coroutines.experimental.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.StatementContext
import org.jetbrains.exposed.sql.statements.StatementInterceptor
import org.jetbrains.exposed.sql.statements.expandArgs
import org.jetbrains.exposed.sql.transactions.*
import org.slf4j.LoggerFactory
import java.sql.Connection
import kotlin.coroutines.experimental.CoroutineContext
fun <T> ctransaction(db: Database? = null, statement: suspend Transaction.() -> T): T {
return ctransaction(TransactionManager.manager.defaultIsolationLevel, 3, db, statement)
}
fun <T> ctransaction(transactionIsolation: Int, repetitionAttempts: Int, db: Database? = null, statement: suspend Transaction.() -> T): T {
return transaction(transactionIsolation, repetitionAttempts, db) {
if (this !is CoroutineTransaction) throw IllegalStateException("ctransaction requires CoroutineTransactionManager.")
val job = async(context = manager.context, start = CoroutineStart.UNDISPATCHED) {
this@transaction.statement()
}
if (job.isActive) {
runBlocking(context = Unconfined) {
job.join()
}
}
job.getCompleted()
}
}
class CoroutineTransactionManager(private val db: Database,
dispatcher: CoroutineDispatcher,
override var defaultIsolationLevel: Int = DEFAULT_ISOLATION_LEVEL) : TransactionManager {
val context: CoroutineDispatcher = TransactionCoroutineDispatcher(dispatcher)
private val transaction = ThreadLocal<CoroutineTransaction?>()
override fun currentOrNull(): Transaction? {
return transaction.get()
?: null
}
override fun newTransaction(isolation: Int): Transaction {
return CoroutineTransaction(this, CoroutineTransactionInterface(db, isolation, transaction)).also { transaction.set(it) }
}
private inner class TransactionCoroutineDispatcher(val delegate: CoroutineDispatcher) : CoroutineDispatcher() {
// When the thread changes, move the transaction to the new thread
override fun dispatch(context: CoroutineContext, block: Runnable) {
val existing = transaction.get()
val newContext: CoroutineContext
if (existing != null) {
transaction.set(null)
newContext = context // + existing
} else {
newContext = context
}
delegate.dispatch(newContext, Runnable {
if (existing != null) {
transaction.set(existing)
}
block.run()
})
}
}
}
private class CoroutineTransaction(val manager: CoroutineTransactionManager,
itf: CoroutineTransactionInterface) : Transaction(itf), CoroutineContext.Element {
companion object Key : CoroutineContext.Key<CoroutineTransaction>
override val key: CoroutineContext.Key<CoroutineTransaction> = Key
}
private class CoroutineTransactionInterface(override val db: Database, isolation: Int, val threadLocal: ThreadLocal<CoroutineTransaction?>) : TransactionInterface {
private val connectionLazy = lazy(LazyThreadSafetyMode.NONE) {
db.connector().apply {
autoCommit = false
transactionIsolation = isolation
}
}
override val connection: Connection
get() = connectionLazy.value
override val outerTransaction: CoroutineTransaction? = threadLocal.get()
override fun commit() {
if (connectionLazy.isInitialized())
connection.commit()
}
override fun rollback() {
if (connectionLazy.isInitialized() && !connection.isClosed) {
connection.rollback()
}
}
override fun close() {
try {
if (connectionLazy.isInitialized()) connection.close()
} finally {
threadLocal.set(outerTransaction)
}
}
}
@@ -4,10 +4,13 @@ package io.dico.parcels2.storage.exposed
import com.zaxxer.hikari.HikariDataSource import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.storage.AddedDataPair
import io.dico.parcels2.storage.Backing import io.dico.parcels2.storage.Backing
import io.dico.parcels2.storage.DataPair import io.dico.parcels2.storage.DataPair
import io.dico.parcels2.util.synchronized
import io.dico.parcels2.util.toUUID import io.dico.parcels2.util.toUUID
import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.*
import kotlinx.coroutines.experimental.channels.ArrayChannel
import kotlinx.coroutines.experimental.channels.LinkedListChannel import kotlinx.coroutines.experimental.channels.LinkedListChannel
import kotlinx.coroutines.experimental.channels.ReceiveChannel import kotlinx.coroutines.experimental.channels.ReceiveChannel
import kotlinx.coroutines.experimental.channels.SendChannel import kotlinx.coroutines.experimental.channels.SendChannel
@@ -21,10 +24,9 @@ import javax.sql.DataSource
class ExposedDatabaseException(message: String? = null) : Exception(message) class ExposedDatabaseException(message: String? = null) : Exception(message)
class ExposedBacking(private val dataSourceFactory: () -> DataSource, class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSize: Int) : Backing {
private val poolSize: Int) : Backing {
override val name get() = "Exposed" override val name get() = "Exposed"
val dispatcher: CoroutineDispatcher = newFixedThreadPoolContext(4, "Parcels StorageThread") override val dispatcher: ThreadPoolDispatcher = newFixedThreadPoolContext(poolSize, "Parcels StorageThread")
private var dataSource: DataSource? = null private var dataSource: DataSource? = null
private var database: Database? = null private var database: Database? = null
@@ -40,6 +42,24 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource,
return channel return channel
} }
override fun <T> openChannelForWriting(action: Backing.(T) -> Unit): SendChannel<T> {
val channel = ArrayChannel<T>(poolSize * 2)
repeat(poolSize) {
launch(dispatcher) {
try {
while (true) {
action(channel.receive())
}
} catch (ex: Exception) {
// channel closed
}
}
}
return channel
}
private fun <T> transaction(statement: Transaction.() -> T) = transaction(database!!, statement) private fun <T> transaction(statement: Transaction.() -> T) = transaction(database!!, statement)
companion object { companion object {
@@ -51,25 +71,54 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource,
} }
override fun init() { override fun init() {
if (isShutdown || isConnected) throw IllegalStateException() synchronized {
if (isShutdown || isConnected) throw IllegalStateException()
dataSource = dataSourceFactory() dataSource = dataSourceFactory()
database = Database.connect(dataSource!!) database = Database.connect(dataSource!!)
transaction(database!!) { transaction(database!!) {
create(WorldsT, OwnersT, ParcelsT, ParcelOptionsT, AddedLocalT, AddedGlobalT) create(WorldsT, ProfilesT, ParcelsT, ParcelOptionsT, AddedLocalT, AddedGlobalT)
}
} }
} }
override fun shutdown() { override fun shutdown() {
if (isShutdown) throw IllegalStateException() synchronized {
dataSource?.let { if (isShutdown) throw IllegalStateException()
(it as? HikariDataSource)?.close() dataSource?.let {
(it as? HikariDataSource)?.close()
}
database = null
isShutdown = true
} }
database = null
isShutdown = true
} }
override fun produceParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>) { private fun PlayerProfile.toOwnerProfile(): PlayerProfile {
if (this is PlayerProfile.Star) return PlayerProfile.Fake(PlayerProfile.Star.name)
return this
}
private fun PlayerProfile.Unresolved.toResolvedProfile(): PlayerProfile.Real {
return resolve(getPlayerUuidForName(name) ?: throwException())
}
private fun PlayerProfile.toResolvedProfile(): PlayerProfile {
if (this is PlayerProfile.Unresolved) return toResolvedProfile()
return this
}
private fun PlayerProfile.toRealProfile(): PlayerProfile.Real = when (this) {
is PlayerProfile.Real -> this
is PlayerProfile.Fake -> throw IllegalArgumentException("Fake profiles are not accepted")
is PlayerProfile.Unresolved -> toResolvedProfile()
else -> throw InternalError("Case should not be reached")
}
override fun getPlayerUuidForName(name: String): UUID? {
return ProfilesT.slice(ProfilesT.uuid).select { ProfilesT.name.upperCase() eq name.toUpperCase() }
.firstOrNull()?.let { it[ProfilesT.uuid]?.toUUID() }
}
override fun transmitParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>) {
for (parcel in parcels) { for (parcel in parcels) {
val data = readParcelData(parcel) val data = readParcelData(parcel)
channel.offer(parcel to data) channel.offer(parcel to data)
@@ -77,25 +126,25 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource,
channel.close() channel.close()
} }
override fun produceAllParcelData(channel: SendChannel<Pair<ParcelId, ParcelData?>>) = ctransaction<Unit> { override fun transmitAllParcelData(channel: SendChannel<DataPair>) {
ParcelsT.selectAll().forEach { row -> ParcelsT.selectAll().forEach { row ->
val parcel = ParcelsT.getId(row) ?: return@forEach val parcel = ParcelsT.getItem(row) ?: return@forEach
val data = rowToParcelData(row) val data = rowToParcelData(row)
channel.send(parcel to data) channel.offer(parcel to data)
} }
channel.close() channel.close()
} }
override fun readParcelData(parcel: ParcelId): ParcelData? = transaction { override fun readParcelData(parcel: ParcelId): ParcelData? {
val row = ParcelsT.getRow(parcel) ?: return@transaction null val row = ParcelsT.getRow(parcel) ?: return null
rowToParcelData(row) return rowToParcelData(row)
} }
override fun getOwnedParcels(user: ParcelOwner): List<ParcelId> = transaction { override fun getOwnedParcels(user: PlayerProfile): List<ParcelId> {
val user_id = OwnersT.getId(user) ?: return@transaction emptyList() val user_id = ProfilesT.getId(user.toOwnerProfile()) ?: return emptyList()
ParcelsT.select { ParcelsT.owner_id eq user_id } return ParcelsT.select { ParcelsT.owner_id eq user_id }
.orderBy(ParcelsT.claim_time, isAsc = true) .orderBy(ParcelsT.claim_time, isAsc = true)
.mapNotNull(ParcelsT::getId) .mapNotNull(ParcelsT::getItem)
.toList() .toList()
} }
@@ -123,21 +172,21 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource,
setParcelOwner(parcel, data.owner) setParcelOwner(parcel, data.owner)
for ((uuid, status) in data.addedMap) { for ((profile, status) in data.addedMap) {
setLocalPlayerStatus(parcel, uuid, status) AddedLocalT.setPlayerStatus(parcel, profile, status)
} }
setParcelAllowsInteractInputs(parcel, data.allowInteractInputs) setParcelAllowsInteractInputs(parcel, data.allowInteractInputs)
setParcelAllowsInteractInventory(parcel, data.allowInteractInventory) setParcelAllowsInteractInventory(parcel, data.allowInteractInventory)
} }
override fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?) = transaction { override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) {
val id = if (owner == null) val id = if (owner == null)
ParcelsT.getId(parcel) ?: return@transaction ParcelsT.getId(parcel) ?: return
else else
ParcelsT.getOrInitId(parcel) ParcelsT.getOrInitId(parcel)
val owner_id = owner?.let { OwnersT.getOrInitId(it) } val owner_id = owner?.let { ProfilesT.getOrInitId(it.toOwnerProfile()) }
val time = owner?.let { DateTime.now() } val time = owner?.let { DateTime.now() }
ParcelsT.update({ ParcelsT.id eq id }) { ParcelsT.update({ ParcelsT.id eq id }) {
@@ -146,11 +195,11 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource,
} }
} }
override fun setLocalPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus) = transaction { override fun setLocalPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus) {
AddedLocalT.setPlayerStatus(parcel, player, status) AddedLocalT.setPlayerStatus(parcel, player.toRealProfile(), status)
} }
override fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean): Unit = transaction { override fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) {
val id = ParcelsT.getOrInitId(parcel) val id = ParcelsT.getOrInitId(parcel)
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
it[ParcelOptionsT.parcel_id] = id it[ParcelOptionsT.parcel_id] = id
@@ -158,7 +207,7 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource,
} }
} }
override fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean): Unit = transaction { override fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) {
val id = ParcelsT.getOrInitId(parcel) val id = ParcelsT.getOrInitId(parcel)
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
it[ParcelOptionsT.parcel_id] = id it[ParcelOptionsT.parcel_id] = id
@@ -166,36 +215,30 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource,
} }
} }
override fun produceAllGlobalAddedData(channel: SendChannel<Pair<ParcelOwner, MutableMap<UUID, AddedStatus>>>) = ctransaction<Unit> { override fun transmitAllGlobalAddedData(channel: SendChannel<AddedDataPair<PlayerProfile>>) {
AddedGlobalT.sendAllAddedData(channel) AddedGlobalT.sendAllAddedData(channel)
channel.close() channel.close()
} }
override fun readGlobalAddedData(owner: ParcelOwner): MutableMap<UUID, AddedStatus> = transaction { override fun readGlobalAddedData(owner: PlayerProfile): MutableAddedDataMap {
return@transaction AddedGlobalT.readAddedData(OwnersT.getId(owner) ?: return@transaction hashMapOf()) return AddedGlobalT.readAddedData(ProfilesT.getId(owner.toOwnerProfile()) ?: return hashMapOf())
} }
override fun setGlobalPlayerStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) = transaction { override fun setGlobalPlayerStatus(owner: PlayerProfile, player: PlayerProfile, status: AddedStatus) {
AddedGlobalT.setPlayerStatus(owner, player, status) AddedGlobalT.setPlayerStatus(owner, player.toRealProfile(), status)
} }
private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply { private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply {
owner = row[ParcelsT.owner_id]?.let { OwnersT.getId(it) } owner = row[ParcelsT.owner_id]?.let { ProfilesT.getItem(it) }
since = row[ParcelsT.claim_time] since = row[ParcelsT.claim_time]
val parcelId = row[ParcelsT.id] val id = row[ParcelsT.id]
addedMap = AddedLocalT.readAddedData(parcelId) ParcelOptionsT.select { ParcelOptionsT.parcel_id eq id }.firstOrNull()?.let { optrow ->
allowInteractInputs = optrow[ParcelOptionsT.interact_inputs]
AddedLocalT.select { AddedLocalT.attach_id eq parcelId }.forEach { allowInteractInventory = optrow[ParcelOptionsT.interact_inventory]
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 { addedMap = AddedLocalT.readAddedData(id)
allowInteractInputs = it[ParcelOptionsT.interact_inputs]
allowInteractInventory = it[ParcelOptionsT.interact_inventory]
}
} }
} }
@@ -1,12 +1,9 @@
package io.dico.parcels2.storage.exposed package io.dico.parcels2.storage.exposed
import org.jetbrains.exposed.sql.Column import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.Index import org.jetbrains.exposed.sql.Function
import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.statements.InsertStatement import org.jetbrains.exposed.sql.statements.InsertStatement
import org.jetbrains.exposed.sql.transactions.TransactionManager import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.transactions.transaction
class UpsertStatement<Key : Any>(table: Table, conflictColumn: Column<*>? = null, conflictIndex: Index? = null) class UpsertStatement<Key : Any>(table: Table, conflictColumn: Column<*>? = null, conflictIndex: Index? = null)
: InsertStatement<Key>(table, false) { : InsertStatement<Key>(table, false) {
@@ -63,3 +60,15 @@ fun Table.indexR(customIndexName: String? = null, isUnique: Boolean = false, var
fun Table.uniqueIndexR(customIndexName: String? = null, vararg columns: Column<*>): Index = indexR(customIndexName, true, *columns) 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>> SqlExpressionBuilder.greater(col1: ExpressionWithColumnType<T>, col2: ExpressionWithColumnType<T>): Expression<T> {
return case(col1)
.When(col1.greater(col2), col1)
.Else(col2)
}
@@ -3,12 +3,12 @@
package io.dico.parcels2.storage.exposed package io.dico.parcels2.storage.exposed
import io.dico.parcels2.ParcelId import io.dico.parcels2.ParcelId
import io.dico.parcels2.ParcelOwner
import io.dico.parcels2.ParcelWorldId import io.dico.parcels2.ParcelWorldId
import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.util.toByteArray import io.dico.parcels2.util.toByteArray
import io.dico.parcels2.util.toUUID import io.dico.parcels2.util.toUUID
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.InsertStatement import org.jetbrains.exposed.sql.statements.UpdateBuilder
import java.util.UUID import java.util.UUID
sealed class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj>, QueryObj>(tableName: String, columnName: String) sealed class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj>, QueryObj>(tableName: String, columnName: String)
@@ -23,16 +23,16 @@ sealed class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj>,
return select { where(table) }.firstOrNull()?.let { it[id] } return select { where(table) }.firstOrNull()?.let { it[id] }
} }
internal inline fun insertAndGetId(objName: String, noinline body: TableT.(InsertStatement<Number>) -> Unit): Int { internal inline fun getOrInitId(getId: () -> Int?, noinline body: TableT.(UpdateBuilder<*>) -> Unit, objName: () -> String): Int {
return table.insert(body)[id] ?: insertError(objName) return getId() ?: table.insertIgnore(body)[id] ?: getId() ?: throw ExposedDatabaseException("This should not happen - failed to insert ${objName()} and get its id")
} }
private inline fun insertError(obj: String): Nothing = throw ExposedDatabaseException("This should not happen - failed to insert $obj and getParcelDeferred its id")
abstract fun getId(obj: QueryObj): Int? abstract fun getId(obj: QueryObj): Int?
abstract fun getOrInitId(obj: QueryObj): Int abstract fun getOrInitId(obj: QueryObj): Int
fun getId(id: Int): QueryObj? = select { this@IdTransactionsTable.id eq id }.firstOrNull()?.let { getId(it) } fun getItem(id: Int): QueryObj? = select { this@IdTransactionsTable.id eq id }.firstOrNull()?.let { getItem(it) }
abstract fun getId(row: ResultRow): QueryObj? 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>("parcel_worlds", "world_id") { object WorldsT : IdTransactionsTable<WorldsT, ParcelWorldId>("parcel_worlds", "world_id") {
@@ -44,14 +44,16 @@ object WorldsT : IdTransactionsTable<WorldsT, ParcelWorldId>("parcel_worlds", "w
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, 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 getId(worldName: String, uid: UUID?): Int? = getId(worldName, uid?.toByteArray())
internal inline fun getOrInitId(worldName: String, worldUid: UUID?): Int = worldUid?.toByteArray().let { binaryUid -> internal inline fun getOrInitId(worldName: String, worldUid: UUID?): Int = worldUid?.toByteArray().let { binaryUid ->
getId(worldName, binaryUid) return getOrInitId(
?: insertAndGetId("world named $worldName") { it[name] = worldName; binaryUid?.let { buid -> it[uid] = buid } } { 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 getId(world: ParcelWorldId): Int? = getId(world.name, world.uid)
override fun getOrInitId(world: ParcelWorldId): Int = getOrInitId(world.name, world.uid) override fun getOrInitId(world: ParcelWorldId): Int = getOrInitId(world.name, world.uid)
override fun getId(row: ResultRow): ParcelWorldId { override fun getItem(row: ResultRow): ParcelWorldId {
return ParcelWorldId(row[name], row[uid]?.toUUID()) return ParcelWorldId(row[name], row[uid]?.toUUID())
} }
} }
@@ -60,7 +62,7 @@ object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("parcels", "parcel_id"
val world_id = integer("world_id").references(WorldsT.id) val world_id = integer("world_id").references(WorldsT.id)
val px = integer("px") val px = integer("px")
val pz = integer("pz") val pz = integer("pz")
val owner_id = integer("owner_id").references(OwnersT.id).nullable() val owner_id = integer("owner_id").references(ProfilesT.id).nullable()
val claim_time = datetime("claim_time").nullable() val claim_time = datetime("claim_time").nullable()
val index_location = uniqueIndexR("index_location", world_id, px, pz) val index_location = uniqueIndexR("index_location", world_id, px, pz)
@@ -68,8 +70,10 @@ object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("parcels", "parcel_id"
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 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 { private inline fun getOrInitId(worldName: String, worldUid: UUID?, parcelX: Int, parcelZ: Int): Int {
val worldId = WorldsT.getOrInitId(worldName, worldUid) val worldId = WorldsT.getOrInitId(worldName, worldUid)
return getId(worldId, parcelX, parcelZ) return getOrInitId(
?: insertAndGetId("parcel at $worldName($parcelX, $parcelZ)") { it[world_id] = worldId; it[px] = parcelX; it[pz] = parcelZ } { 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 getId(parcel: ParcelId): Int? = getId(parcel.worldId.name, parcel.worldId.uid, parcel.x, parcel.z)
@@ -78,41 +82,56 @@ object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("parcels", "parcel_id"
private inline fun getRow(id: Int): ResultRow? = select { ParcelsT.id eq id }.firstOrNull() private inline fun getRow(id: Int): ResultRow? = select { ParcelsT.id eq id }.firstOrNull()
fun getRow(parcel: ParcelId): ResultRow? = getId(parcel)?.let { getRow(it) } fun getRow(parcel: ParcelId): ResultRow? = getId(parcel)?.let { getRow(it) }
override fun getId(row: ResultRow): ParcelId? { override fun getItem(row: ResultRow): ParcelId? {
val worldId = row[world_id] val worldId = row[world_id]
val world = WorldsT.getId(worldId) ?: return null val world = WorldsT.getItem(worldId) ?: return null
return ParcelId(world, row[px], row[pz]) return ParcelId(world, row[px], row[pz])
} }
} }
object OwnersT : IdTransactionsTable<OwnersT, ParcelOwner>("parcel_owners", "owner_id") { object ProfilesT : IdTransactionsTable<ProfilesT, PlayerProfile>("parcel_profiles", "owner_id") {
val uuid = binary("uuid", 16).nullable() val uuid = binary("uuid", 16).nullable()
val name = varchar("name", 32) val name = varchar("name", 32)
val index_pair = uniqueIndexR("index_pair", uuid, name) val index_pair = uniqueIndexR("index_pair", uuid, name)
private inline fun getId(binaryUuid: ByteArray) = getId { uuid eq binaryUuid } private inline fun getId(binaryUuid: ByteArray) = getId { uuid eq binaryUuid }
private inline fun getId(uuid: UUID) = getId(uuid.toByteArray()) private inline fun getId(uuid: UUID) = getId(uuid.toByteArray())
private inline fun getId(nameIn: String) = getId { uuid.isNull() and (name eq nameIn) } 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 -> private inline fun getOrInitId(uuid: UUID, name: String) = uuid.toByteArray().let { binaryUuid -> getOrInitId(
getId(binaryUuid) ?: insertAndGetId("owner(uuid = $uuid)") { { getId(binaryUuid) },
it[this@OwnersT.uuid] = binaryUuid { it[this@ProfilesT.uuid] = binaryUuid; it[this@ProfilesT.name] = name },
it[this@OwnersT.name] = name { "profile(uuid = $uuid, name = $name)" })
}
} }
private inline fun getOrInitId(name: String) = private inline fun getOrInitId(name: String) = getOrInitId(
getId(name) ?: insertAndGetId("owner(name = $name)") { it[OwnersT.name] = name } { getId(name) },
{ it[ProfilesT.name] = name },
{ "owner(name = $name)" })
override fun getId(owner: ParcelOwner): Int? =
if (owner.hasUUID) getId(owner.uuid!!)
else getId(owner.name!!)
override fun getOrInitId(owner: ParcelOwner): Int = override fun getId(profile: PlayerProfile): Int? = when (profile) {
if (owner.hasUUID) getOrInitId(owner.uuid!!, owner.notNullName) is PlayerProfile.Real -> getId(profile.uuid)
else getOrInitId(owner.name!!) is PlayerProfile.Fake -> getId(profile.name)
is PlayerProfile.Unresolved -> getRealId(profile.name)
override fun getId(row: ResultRow): ParcelOwner { else -> throw IllegalArgumentException()
return row[uuid]?.toUUID()?.let { ParcelOwner(it) } ?: ParcelOwner(row[name])
} }
override fun getOrInitId(profile: PlayerProfile): Int = when (profile) {
is PlayerProfile.Real -> getOrInitId(profile.uuid, profile.notNullName)
is PlayerProfile.Fake -> getOrInitId(profile.name)
else -> throw IllegalArgumentException()
}
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
}
} }
// val ParcelsWithOptionsT = ParcelsT.join(ParcelOptionsT, JoinType.INNER, onColumn = ParcelsT.id, otherColumn = ParcelOptionsT.parcel_id)
@@ -2,17 +2,13 @@
package io.dico.parcels2.storage.exposed package io.dico.parcels2.storage.exposed
import io.dico.parcels2.AddedStatus import io.dico.parcels2.*
import io.dico.parcels2.ParcelId
import io.dico.parcels2.ParcelOwner
import io.dico.parcels2.util.toByteArray
import io.dico.parcels2.util.toUUID
import kotlinx.coroutines.experimental.channels.SendChannel import kotlinx.coroutines.experimental.channels.SendChannel
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import java.util.UUID import java.util.UUID
object AddedLocalT : AddedTable<ParcelId>("parcels_added_local", ParcelsT) object AddedLocalT : AddedTable<ParcelId>("parcels_added_local", ParcelsT)
object AddedGlobalT : AddedTable<ParcelOwner>("parcels_added_global", OwnersT) object AddedGlobalT : AddedTable<PlayerProfile>("parcels_added_global", ProfilesT)
object ParcelOptionsT : Table("parcel_options") { object ParcelOptionsT : Table("parcel_options") {
val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE) val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE)
@@ -20,55 +16,59 @@ object ParcelOptionsT : Table("parcel_options") {
val interact_inputs = bool("interact_inputs").default(true) val interact_inputs = bool("interact_inputs").default(true)
} }
typealias AddedStatusSendChannel<AttachT> = SendChannel<Pair<AttachT, MutableMap<UUID, AddedStatus>>> typealias AddedStatusSendChannel<AttachT> = SendChannel<Pair<AttachT, MutableAddedDataMap>>
sealed class AddedTable<AttachT>(name: String, val idTable: IdTransactionsTable<*, AttachT>) : 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 attach_id = integer("attach_id").references(idTable.id, ReferenceOption.CASCADE)
val player_uuid = binary("player_uuid", 16) val profile_id = integer("profile_id").references(ProfilesT.id, ReferenceOption.CASCADE)
val allowed_flag = bool("allowed_flag") val allowed_flag = bool("allowed_flag")
val index_pair = uniqueIndexR("index_pair", attach_id, player_uuid) val index_pair = uniqueIndexR("index_pair", attach_id, profile_id)
fun setPlayerStatus(attachedOn: AttachT, player: UUID, status: AddedStatus) {
val binaryUuid = player.toByteArray()
fun setPlayerStatus(attachedOn: AttachT, player: PlayerProfile.Real, status: AddedStatus) {
if (status.isDefault) { if (status.isDefault) {
idTable.getId(attachedOn)?.let { id -> val player_id = ProfilesT.getId(player) ?: return
deleteWhere { (attach_id eq id) and (player_uuid eq binaryUuid) } idTable.getId(attachedOn)?.let { holder ->
deleteWhere { (attach_id eq holder) and (profile_id eq player_id) }
} }
return return
} }
val id = idTable.getOrInitId(attachedOn) val holder = idTable.getOrInitId(attachedOn)
val player_id = ProfilesT.getOrInitId(player)
upsert(conflictIndex = index_pair) { upsert(conflictIndex = index_pair) {
it[attach_id] = id it[attach_id] = holder
it[player_uuid] = binaryUuid it[profile_id] = player_id
it[allowed_flag] = status.isAllowed it[allowed_flag] = status.isAllowed
} }
} }
fun readAddedData(id: Int): MutableMap<UUID, AddedStatus> { fun readAddedData(id: Int): MutableAddedDataMap {
return slice(player_uuid, allowed_flag).select { attach_id eq id } val list = slice(profile_id, allowed_flag).select { attach_id eq id }
.associateByTo(hashMapOf(), { it[player_uuid].toUUID() }, { it[allowed_flag].asAddedStatus() }) val result = MutableAddedDataMap()
for (row in list) {
val profile = ProfilesT.getRealItem(row[profile_id]) ?: continue
result[profile] = row[allowed_flag].asAddedStatus()
}
return result
} }
suspend fun sendAllAddedData(channel: AddedStatusSendChannel<AttachT>) { fun sendAllAddedData(channel: AddedStatusSendChannel<AttachT>) {
/*
val iterator = selectAll().orderBy(attach_id).iterator() val iterator = selectAll().orderBy(attach_id).iterator()
if (iterator.hasNext()) { if (iterator.hasNext()) {
val firstRow = iterator.next() val firstRow = iterator.next()
var id: Int = firstRow[attach_id] var id: Int = firstRow[attach_id]
var attach: SerializableT? = null var attach: AttachT? = null
var map: MutableMap<UUID, AddedStatus>? = null var map: MutableAddedDataMap? = null
fun initAttachAndMap() { fun initAttachAndMap() {
attach = idTable.getId(id) attach = idTable.getItem(id)
map = attach?.let { mutableMapOf() } map = attach?.let { mutableMapOf() }
} }
suspend fun sendIfPresent() { fun sendIfPresent() {
if (attach != null && map != null && map!!.isNotEmpty()) { if (attach != null && map != null && map!!.isNotEmpty()) {
channel.send(attach!! to map!!) channel.offer(attach!! to map!!)
} }
attach = null attach = null
map = null map = null
@@ -88,13 +88,13 @@ sealed class AddedTable<AttachT>(name: String, val idTable: IdTransactionsTable<
continue // owner not found for this owner id continue // owner not found for this owner id
} }
val player_uuid = row[player_uuid].toUUID() val profile = ProfilesT.getRealItem(row[profile_id]) ?: continue
val status = row[allowed_flag].asAddedStatus() val status = row[allowed_flag].asAddedStatus()
map!![player_uuid] = status map!![profile] = status
} }
sendIfPresent() sendIfPresent()
}*/ }
} }
private inline fun Boolean?.asAddedStatus() = if (this == null) AddedStatus.DEFAULT else if (this) AddedStatus.ALLOWED else AddedStatus.BANNED private inline fun Boolean?.asAddedStatus() = if (this == null) AddedStatus.DEFAULT else if (this) AddedStatus.ALLOWED else AddedStatus.BANNED
@@ -6,45 +6,43 @@ import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.options.PlotmeMigrationOptions import io.dico.parcels2.options.PlotmeMigrationOptions
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import io.dico.parcels2.storage.exposed.abs
import io.dico.parcels2.storage.exposed.greater
import io.dico.parcels2.storage.migration.Migration 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.toUUID
import io.dico.parcels2.util.uuid import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.launch
import org.bukkit.Bukkit import kotlinx.coroutines.experimental.newFixedThreadPoolContext
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.ByteArrayOutputStream
import java.sql.Blob import java.sql.Blob
import java.util.UUID import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import javax.sql.DataSource import javax.sql.DataSource
import kotlin.coroutines.experimental.coroutineContext
class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration { class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration {
private var dataSource: DataSource? = null private var dataSource: DataSource? = null
private var database: Database? = null private var database: Database? = null
private var isShutdown: Boolean = false private var isShutdown: Boolean = false
private val mlogger = LoggerFactory.getLogger("PlotMe Migrator") private val mlogger = LoggerFactory.getLogger("PlotMe Migrator")
val dispatcher = newFixedThreadPoolContext(1, "PlotMe Migration Thread")
private fun <T> transaction(statement: Transaction.() -> T) = org.jetbrains.exposed.sql.transactions.transaction(database!!, statement) private fun <T> transaction(statement: Transaction.() -> T) = org.jetbrains.exposed.sql.transactions.transaction(database!!, statement)
override fun migrateTo(storage: Storage): Job { override fun migrateTo(storage: Storage): Job {
return launch(context = storage.asyncDispatcher) { return launch(dispatcher) {
init() init()
transaction { launch(context = Unconfined, start = CoroutineStart.UNDISPATCHED) { doWork(storage) } } doWork(storage)
shutdown() shutdown()
} }
} }
suspend fun init() { fun init() {
if (isShutdown) throw IllegalStateException() if (isShutdown || database != null) throw IllegalStateException()
dataSource = options.storage.getDataSourceFactory()!!() dataSource = options.storage.getDataSourceFactory()!!()
database = Database.connect(dataSource!!) database = Database.connect(dataSource!!)
} }
suspend fun shutdown() { fun shutdown() {
if (isShutdown) throw IllegalStateException() if (isShutdown) throw IllegalStateException()
dataSource?.let { dataSource?.let {
(it as? HikariDataSource)?.close() (it as? HikariDataSource)?.close()
@@ -53,74 +51,61 @@ class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration {
isShutdown = true isShutdown = true
} }
private val parcelsCache = hashMapOf<String, MutableMap<Vec2i, ParcelData>>() suspend fun doWork(target: Storage) {
val exit = transaction {
(!PlotmePlotsT.exists()).also {
if (it) mlogger.warn("Plotme tables don't appear to exist. Exiting.")
}
}
if (exit) return
private fun getMap(worldName: String): MutableMap<Vec2i, ParcelData>? { val worldCache = options.worldsFromTo.mapValues { ParcelWorldId(it.value) }
val mapped = options.worldsFromTo[worldName] ?: return null
return parcelsCache.computeIfAbsent(mapped) { mutableMapOf() }
}
private fun getData(worldName: String, position: Vec2i): ParcelData? { fun getParcelId(table: PlotmeTable, row: ResultRow): ParcelId? {
return getMap(worldName)?.computeIfAbsent(position) { ParcelDataHolder(addedMap = ConcurrentHashMap()) } val world = worldCache[row[table.world_name]] ?: return null
} return ParcelId(world, row[table.px], row[table.pz])
suspend fun doWork(target: Storage): Unit {
if (!PlotmePlotsT.exists()) {
mlogger.warn("Plotme tables don't appear to exist. Exiting.")
return
} }
parcelsCache.clear() fun PlotmePlotPlayerMap.transmitPlotmeAddedTable(kind: AddedStatus) {
selectAll().forEach { row ->
iterPlotmeTable(PlotmePlotsT) { data, row -> val parcel = getParcelId(this, row) ?: return@forEach
// in practice, owner_uuid is not null for any plot currently. It will convert well. val profile = StatusKey.safe(row[player_uuid]?.toUUID(), row[player_name]) ?: return@forEach
data.owner = ParcelOwner(row[owner_uuid]?.toUUID(), row[owner_name]) target.setParcelPlayerStatus(parcel, profile, kind)
}
launch(context = target.asyncDispatcher) {
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)
} }
} }
launch(context = target.asyncDispatcher) { mlogger.info("Transmitting data from plotmeplots table")
iterPlotmeTable(PlotmeDeniedT) { data, row -> transaction {
val uuid = row[player_uuid]?.toUUID() PlotmePlotsT.selectAll()
?: Bukkit.getOfflinePlayer(row[player_name]).takeIf { it.isValid }?.uuid .orderBy(PlotmePlotsT.world_name)
?: return@iterPlotmeTable .orderBy(with(SqlExpressionBuilder) { greater(PlotmePlotsT.px.abs(), PlotmePlotsT.pz.abs()) })
.forEach { row ->
data.setAddedStatus(uuid, AddedStatus.BANNED) val parcel = getParcelId(PlotmePlotsT, row) ?: return@forEach
} val owner = PlayerProfile.safe(row[PlotmePlotsT.owner_uuid]?.toUUID(), row[PlotmePlotsT.owner_name])
target.setParcelOwner(parcel, owner)
}
} }
println(coroutineContext[Job]!!.children) mlogger.info("Transmitting data from plotmeallowed table")
coroutineContext[Job]!!.joinChildren() transaction {
PlotmeAllowedT.transmitPlotmeAddedTable(AddedStatus.ALLOWED)
for ((worldName, map) in parcelsCache) {
val world = ParcelWorldId(worldName)
for ((pos, data) in map) {
val parcel = ParcelId(world, pos)
target.setParcelData(parcel, data)
}
} }
} mlogger.info("Transmitting data from plotmedenied table")
transaction {
private fun Blob.toUUID(): UUID { PlotmeDeniedT.transmitPlotmeAddedTable(AddedStatus.BANNED)
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)
} }
mlogger.warn("Data has been **transmitted**.")
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()
}
} }
@@ -15,6 +15,9 @@ fun File.tryCreate(): Boolean {
return true return true
} }
inline fun Boolean.alsoIfTrue(block: () -> Unit): Boolean = also { if (it) block() }
inline fun Boolean.alsoIfFalse(block: () -> Unit): Boolean = also { if (!it) block() }
inline fun <R> Any.synchronized(block: () -> R): R = synchronized(this, block) inline fun <R> Any.synchronized(block: () -> R): R = synchronized(this, block)
inline fun <T> T?.isNullOr(condition: T.() -> Boolean): Boolean = this == null || condition() inline fun <T> T?.isNullOr(condition: T.() -> Boolean): Boolean = this == null || condition()
@@ -2,12 +2,14 @@ package io.dico.parcels2.util
import io.dico.dicore.Formatting import io.dico.dicore.Formatting
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.logger import io.dico.parcels2.logger
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.plugin.java.JavaPlugin import org.bukkit.plugin.java.JavaPlugin
inline val OfflinePlayer.uuid get() = uniqueId inline val OfflinePlayer.uuid get() = uniqueId
@Suppress("UsePropertyAccessSyntax") @Suppress("UsePropertyAccessSyntax")
inline val OfflinePlayer.isValid inline val OfflinePlayer.isValid
get() = isOnline() || hasPlayedBefore() get() = isOnline() || hasPlayedBefore()
+7 -31
View File
@@ -1,41 +1,17 @@
<configuration> <configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder> <encoder>
<!-- old pattern <pattern>%d{HH:mm:ss.SSS} %magenta(%-8.-8(%thread)) %highlight(%-5level) %boldCyan(%8.-32logger{32}) - %msg</pattern>--> <!-- old pattern <pattern>%d{HH:mm:ss.SSS} %magenta(%-8.-8(%thread)) %highlight(%-5level) %boldCyan(%8.-32logger{32}) - %msg\n</pattern>-->
<pattern>%magenta(%-8.-8(%thread)) %highlight(%-5level) %boldCyan(%6.-32logger{32}) - %msg</pattern> <pattern>%magenta(%-16.-16(%thread)) %highlight(%-5level) %boldCyan(%6.-32logger{32}) - %msg</pattern>
</encoder> </encoder>
</appender> </appender>
<root level="info"> <root level="debug">
<appender-ref ref="STDOUT" /> <appender-ref ref="STDOUT" />
</root> </root>
<appender name="SQLLOG" class="ch.qos.logback.core.RollingFileAppender"> <logger name="com.zaxxer.hikari.pool.HikariPool" level="info"/>
<append>true</append> <logger name="com.zaxxer.hikari.pool.PoolBase" level="info"/>
<file>C:/Parcels/sql.log</file> <logger name="com.zaxxer.hikari.HikariConfig" level="info"/>
<logger name="Exposed" level="info"/>
<encoder>
<!-- old pattern <pattern>%d{HH:mm:ss.SSS} %magenta(%-8.-8(%thread)) %highlight(%-5level) %boldCyan(%8.-32logger{32}) - %msg</pattern>-->
<pattern>%magenta(%-8.-8(%thread)) %highlight(%-5level) %boldCyan(%6.-32logger{32}) - %msg</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>C:/Parcels/sql%i.log</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>3</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>1MB</maxFileSize>
</triggeringPolicy>
</appender>
<appender name="ASYNC_SQLLOG" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="SQLLOG"/>
<neverBlock>true</neverBlock>
</appender>
<logger name="Exposed" level="DEBUG">
<appender-ref ref="ASYNC_SQLLOG"/>
</logger>
</configuration> </configuration>